diff --git a/build.gradle.kts b/build.gradle.kts index efbfd302..5785e134 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,7 +83,7 @@ dependencies { implementation("com.github.jnr:jnr-ffi:2.2.13") implementation("ru.dbotthepony:kbox2d:2.4.1.6") - implementation("ru.dbotthepony:kvector:2.8.0") + implementation("ru.dbotthepony:kvector:2.9.0") implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 93271034..f34939ae 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -9,6 +9,7 @@ import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFWErrorCallback import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL15 import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GLCapabilities import org.lwjgl.system.MemoryStack @@ -33,9 +34,8 @@ import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram -import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType -import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.input.UserInput @@ -49,22 +49,23 @@ import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity +import ru.dbotthepony.kstarbound.util.forEachValid import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kvector.api.IStruct4f -import ru.dbotthepony.kvector.arrays.Matrix4f -import ru.dbotthepony.kvector.arrays.Matrix4fStack +import ru.dbotthepony.kvector.arrays.Matrix3f +import ru.dbotthepony.kvector.arrays.Matrix3fStack import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2i -import ru.dbotthepony.kvector.vector.Vector3f import java.io.Closeable import java.io.File import java.lang.ref.Cleaner +import java.lang.ref.WeakReference import java.nio.ByteBuffer import java.nio.ByteOrder import java.time.Duration @@ -106,19 +107,13 @@ class StarboundClient : Closeable { var clientTerminated = false private set - /** - * Матрица преобразования экранных координат (в пикселях) в нормализованные координаты - */ - var viewportMatrixScreen: Matrix4f + var viewportMatrixScreen: Matrix3f private set - get() = Matrix4f.unmodifiable(field) + get() = Matrix3f.unmodifiable(field) - /** - * Матрица преобразования мировых координат в нормализованные координаты - */ - var viewportMatrixWorld: Matrix4f + var viewportMatrixWorld: Matrix3f private set - get() = Matrix4f.unmodifiable(field) + get() = Matrix3f.unmodifiable(field) var isRenderingGame = true private set @@ -140,12 +135,10 @@ class StarboundClient : Closeable { thread } - @Volatile - var objectsCleaned = 0L + var objectsCreated = 0L private set - @Volatile - var gcHits = 0L + var objectsCleaned = 0L private set init { @@ -235,12 +228,19 @@ class StarboundClient : Closeable { GLFW.glfwShowWindow(window) putDebugLog("Initialized GLFW window") + + val v = glGenBuffers() + glBindBuffer(GL_ARRAY_BUFFER, v) + glBindBuffer(GL_ARRAY_BUFFER, 0) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) + GL15.glDeleteBuffers(v) } - val flat2DLines by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.LINES) } - val flat2DTriangles by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.TRIANGLES) } - val flat2DTexturedQuads by lazy { StreamVertexBuilder(GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS) } - val quadWireframe by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME) } + val maxTextureBlocks = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) + val maxVertexAttribBindPoints = glGetInteger(GL_MAX_VERTEX_ATTRIB_BINDINGS) + + val stack = Matrix3fStack() // минимальное время хранения 5 минут и... private val named2DTextures0: Cache = Caffeine.newBuilder() @@ -260,24 +260,26 @@ class StarboundClient : Closeable { } private val missingTexturePath = "/assetmissing.png" + private val regularShaderPrograms = ArrayList>() - val matrixStack = Matrix4fStack() - val freeType = FreeType() - val font = Font() - val box2dRenderer = Box2DRenderer() + fun addShaderProgram(program: GLShaderProgram) { + if (program is GLShaderProgram.Regular) { + regularShaderPrograms.add(WeakReference(program)) + } + } fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable { - val cleanable = cleaner.register(ref) { - objectsCleaned++ + objectsCreated++ + val cleanable = cleaner.register(ref) { if (isSameThread()) { + objectsCleaned++ fn(nativeRef) checkForGLError() } else { - gcHits++ - synchronized(cleanerBacklog) { cleanerBacklog.add { + objectsCleaned++ fn(nativeRef) checkForGLError() } @@ -318,13 +320,10 @@ class StarboundClient : Closeable { var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST) - var vbo by GLObjectTracker(::glBindBuffer, GL_ARRAY_BUFFER) - var ebo by GLObjectTracker(::glBindBuffer, GL_ELEMENT_ARRAY_BUFFER) var vao by GLObjectTracker(::glBindVertexArray) var framebuffer by GLObjectTracker(::glBindFramebuffer, GL_FRAMEBUFFER) var program by GLObjectTracker(::glUseProgram) - val maxTextureBlocks = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) private val textures = Array(maxTextureBlocks) { GLObjectTracker(GL11::glBindTexture, GL_TEXTURE_2D) } var activeTexture = 0 @@ -352,6 +351,9 @@ class StarboundClient : Closeable { glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum) } + val freeType = FreeType() + val font = Font() + val box2dRenderer = Box2DRenderer() val programs = GLPrograms() init { @@ -448,61 +450,24 @@ class StarboundClient : Closeable { } } - fun newVBO() = BufferObject.VBO() fun newEBO() = BufferObject.EBO() + fun newVBO() = BufferObject.VBO() fun newVAO() = VertexArrayObject() inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) { - val builder = quadWireframe + val builder = programs.position.builder - builder.builder.begin() + builder.builder.begin(GeometryType.QUADS_AS_LINES_WIREFRAME) lambda.invoke(builder.builder) builder.upload() - programs.flat.use() - programs.flat.color = color - programs.flat.transform = matrixStack.last() + programs.position.use() + programs.position.colorMultiplier = color + programs.position.worldMatrix = stack.last() builder.draw(GL_LINES) } - inline fun quadColor(lambda: (VertexBuilder) -> Unit) { - val builder = programs.flatColor.builder - - builder.builder.begin() - lambda.invoke(builder.builder) - builder.upload() - - programs.flatColor.use() - programs.flatColor.transform = matrixStack.last() - - builder.draw(GL_TRIANGLES) - } - - inline fun quadTexture(texture: GLTexture2D, lambda: (VertexBuilder) -> Unit) { - val builder = programs.textured2d.builder - - builder.builder.begin() - lambda.invoke(builder.builder) - builder.upload() - - activeTexture = 0 - texture.bind() - - programs.textured2d.use() - programs.textured2d.transform = matrixStack.last() - programs.textured2d.texture = 0 - - builder.draw(GL_TRIANGLES) - } - - inline fun quadWireframe(value: AABB, color: RGBAColor = RGBAColor.WHITE, chain: (VertexBuilder) -> Unit = {}) { - quadWireframe(color) { - it.quad(value.mins.x.toFloat(), value.mins.y.toFloat(), value.maxs.x.toFloat(), value.maxs.y.toFloat()) - chain(it) - } - } - fun vertex(file: File) = GLShader(file, GL_VERTEX_SHADER) fun fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER) @@ -533,12 +498,12 @@ class StarboundClient : Closeable { } } - private fun updateViewportMatrixScreen(): Matrix4f { - return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f)) + private fun updateViewportMatrixScreen(): Matrix3f { + return Matrix3f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat()) } - private fun updateViewportMatrixWorld(): Matrix4f { - return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f) + private fun updateViewportMatrixWorld(): Matrix3f { + return Matrix3f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat()) } private val xMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer() @@ -764,12 +729,14 @@ class StarboundClient : Closeable { clearColor = RGBAColor.SLATE_GRAY glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) - matrixStack.clear(viewportMatrixWorld) + stack.clear(Matrix3f.identity()) - matrixStack.push().last() - .translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира + val viewMatrix = viewportMatrixWorld.copy() + .translate(viewportWidth / 2f, viewportHeight / 2f) // центр экрана + координаты отрисовки мира .scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера - .translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере + .translate(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере + + regularShaderPrograms.forEachValid { it.viewMatrix = viewMatrix } for (lambda in onPreDrawWorld) { lambda.invoke(layers) @@ -786,7 +753,7 @@ class StarboundClient : Closeable { layers = layers, size = viewportRectangle) - layers.render(matrixStack) + layers.render() val viewportLightingMem = viewportLightingMem @@ -818,7 +785,7 @@ class StarboundClient : Closeable { blendFunc = BlendFunc.MULTIPLY_BY_SRC - quadTexture(viewportLightingTexture) { + /*quadTexture(viewportLightingTexture) { it.quad( (viewportCellX).toFloat(), (viewportCellY).toFloat(), @@ -826,7 +793,7 @@ class StarboundClient : Closeable { (viewportCellY + viewportCellHeight).toFloat(), QuadTransformers.uv() ) - } + }*/ blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA } @@ -836,11 +803,9 @@ class StarboundClient : Closeable { for (lambda in onPostDrawWorld) { lambda.invoke() } - - matrixStack.pop() } - matrixStack.clear(viewportMatrixScreen) + regularShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } val thisTime = System.currentTimeMillis() @@ -851,22 +816,24 @@ class StarboundClient : Closeable { alpha = (finishStartupRendering - thisTime) / 1000f } - matrixStack.push() - matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat()) + stack.push() + stack.last().translate(y = viewportHeight.toFloat()) var shade = 255 for (i in startupTextList.size - 1 downTo 0) { val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) - matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f) + stack.last().translate(y = -size.height * 1.2f) if (shade > 120) { shade -= 10 } } - matrixStack.pop() + stack.pop() } + stack.clear(Matrix3f.identity()) + for (fn in onDrawGUI) { fn.invoke() } @@ -876,6 +843,7 @@ class StarboundClient : Closeable { font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f) font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f) + font.render("OGL A: ${objectsCreated - objectsCleaned} D: $objectsCleaned", y = font.lineHeight * 1.8f, scale = 0.4f) GLFW.glfwSwapBuffers(window) GLFW.glfwPollEvents() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/BufferObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/BufferObject.kt index 0a1d9e70..7d6d6b47 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/BufferObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/BufferObject.kt @@ -5,23 +5,26 @@ import org.lwjgl.system.MemoryUtil import ru.dbotthepony.kstarbound.client.StarboundClient import java.nio.ByteBuffer -sealed class BufferObject(val glType: Int) : GLObject() { +sealed class BufferObject : GLObject() { final override val client = StarboundClient.current() - final override val pointer = glGenBuffers() + abstract val glType: Int - init { - checkForGLError("Creating Vertex Buffer Object") - client.registerCleanable(this, ::glDeleteBuffers, pointer) + override fun bind() { + // do nothing } - fun bufferData(data: ByteBuffer, usage: Int): BufferObject { + override fun unbind() { + // do nothing + } + + open fun bufferData(data: ByteBuffer, usage: Int): BufferObject { client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } - fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject { + open fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject { client.ensureSameThread() if (length > data.remaining().toLong()) { @@ -34,51 +37,103 @@ sealed class BufferObject(val glType: Int) : GLObject() { return this } - fun bufferData(data: IntArray, usage: Int): BufferObject { + open fun bufferData(data: IntArray, usage: Int): BufferObject { client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } - fun bufferData(data: FloatArray, usage: Int): BufferObject { + open fun bufferData(data: FloatArray, usage: Int): BufferObject { client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } - fun bufferData(data: DoubleArray, usage: Int): BufferObject { + open fun bufferData(data: DoubleArray, usage: Int): BufferObject { client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } - fun bufferData(data: LongArray, usage: Int): BufferObject { + open fun bufferData(data: LongArray, usage: Int): BufferObject { client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } - class VBO : BufferObject(GL_ARRAY_BUFFER) { - override fun bind() { - client.vbo = this + class EBO : BufferObject() { + override val glType: Int + get() = GL_ELEMENT_ARRAY_BUFFER + + override val pointer: Int = glCreateBuffers() + + init { + checkForGLError("Creating Element Buffer Object") + client.registerCleanable(this, ::glDeleteBuffers, pointer) } - override fun unbind() { - if (client.vbo == this) client.vbo = null + override fun bufferData(data: ByteBuffer, usage: Int): EBO { + return super.bufferData(data, usage) as EBO + } + + override fun bufferData(data: ByteBuffer, usage: Int, length: Long): EBO { + return super.bufferData(data, usage, length) as EBO + } + + override fun bufferData(data: IntArray, usage: Int): EBO { + return super.bufferData(data, usage) as EBO + } + + override fun bufferData(data: FloatArray, usage: Int): EBO { + return super.bufferData(data, usage) as EBO + } + + override fun bufferData(data: DoubleArray, usage: Int): EBO { + return super.bufferData(data, usage) as EBO + } + + override fun bufferData(data: LongArray, usage: Int): EBO { + return super.bufferData(data, usage) as EBO } } - class EBO : BufferObject(GL_ELEMENT_ARRAY_BUFFER) { - override fun bind() { - client.ebo = this + class VBO : BufferObject() { + override val glType: Int + get() = GL_ARRAY_BUFFER + + override val pointer: Int = glCreateBuffers() + + init { + checkForGLError("Creating Vertex Buffer Object") + client.registerCleanable(this, ::glDeleteBuffers, pointer) } - override fun unbind() { - if (client.ebo == this) client.ebo = null + override fun bufferData(data: ByteBuffer, usage: Int): VBO { + return super.bufferData(data, usage) as VBO + } + + override fun bufferData(data: ByteBuffer, usage: Int, length: Long): VBO { + return super.bufferData(data, usage, length) as VBO + } + + override fun bufferData(data: IntArray, usage: Int): VBO { + return super.bufferData(data, usage) as VBO + } + + override fun bufferData(data: FloatArray, usage: Int): VBO { + return super.bufferData(data, usage) as VBO + } + + override fun bufferData(data: DoubleArray, usage: Int): VBO { + return super.bufferData(data, usage) as VBO + } + + override fun bufferData(data: LongArray, usage: Int): VBO { + return super.bufferData(data, usage) as VBO } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLType.kt index 51b189b5..5a443d97 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLType.kt @@ -3,25 +3,27 @@ package ru.dbotthepony.kstarbound.client.gl import org.lwjgl.opengl.GL45.* enum class GLType( - val identity: Int, - val typeIndentity: Int, + val type: Int, + val elementType: Int, val byteSize: Int, - val logicalSize: Int, + val elementSize: Int, + // location(position) width + val width: Int ) { - INT(GL_INT, GL_INT, 4, 1), - UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1), - FLOAT(GL_FLOAT, GL_FLOAT, 4, 1), - DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1), + INT(GL_INT, GL_INT, 4, 1, 1), + UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1, 1), + FLOAT(GL_FLOAT, GL_FLOAT, 4, 1, 1), + DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1, 1), - VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2), - VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3), - VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4), + VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2, 1), + VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3, 1), + VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4, 1), - VEC2I(GL_INT_VEC2, GL_INT, 8, 2), - VEC3I(GL_INT_VEC3, GL_INT, 12, 3), - VEC4I(GL_INT_VEC4, GL_INT, 16, 4), + VEC2I(GL_INT_VEC2, GL_INT, 8, 2, 1), + VEC3I(GL_INT_VEC3, GL_INT, 12, 3, 1), + VEC4I(GL_INT_VEC4, GL_INT, 16, 4, 1), - MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2), - MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3), - MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4), + MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2, 2), + MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3, 3), + MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4, 4), } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt index 1a8594ee..65fa1bea 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt @@ -1,17 +1,39 @@ package ru.dbotthepony.kstarbound.client.gl +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import org.lwjgl.opengl.GL45.* import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes +import java.util.BitSet class VertexArrayObject : GLObject() { override val client = StarboundClient.current() - override val pointer = glGenVertexArrays() + override val pointer = glCreateVertexArrays() init { - checkForGLError() + checkForGLError("Creating Vertex Array Object") client.registerCleanable(this, ::glDeleteVertexArrays, pointer) } + private val enabledAttributes = BitSet() + private val boundBuffers = Int2ObjectAVLTreeMap() + private data class BindConfig(val buffer: BufferObject, val stride: Int, val offset: Long) + + var elementBuffer: BufferObject.EBO? = null + set(value) { + client.ensureSameThread() + + if (field != value) { + if (value == null) + glVertexArrayElementBuffer(pointer, 0) + else + glVertexArrayElementBuffer(pointer, value.pointer) + + checkForGLError() + field = value + } + } + override fun bind() { client.vao = this } @@ -21,18 +43,89 @@ class VertexArrayObject : GLObject() { client.vao = null } - fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): VertexArrayObject { + fun bindBufferToIndex(buffer: BufferObject.VBO, index: Int, vertexStride: Int, offsetFromBeginning: Long = 0L): VertexArrayObject { client.ensureSameThread() - glVertexAttribPointer(position, size, type, normalize, stride, offset) - checkForGLError() + require(index >= 0) { "Invalid bind index: $index" } + require(index < client.maxVertexAttribBindPoints) { "Bind index is too big: $index; Max ${client.maxVertexAttribBindPoints}" } + val config = BindConfig(buffer, vertexStride, offsetFromBeginning) + + if (boundBuffers[index] != config) { + glVertexArrayVertexBuffer(pointer, index, buffer.pointer, offsetFromBeginning, vertexStride) + checkForGLError() + boundBuffers[index] = config + } + return this } + fun unbindBufferFromIndex(index: Int): VertexArrayObject { + client.ensureSameThread() + require(index >= 0) { "Invalid attribute index: $index" } + require(index < client.maxVertexAttribBindPoints) { "Bind index is too big: $index; Max ${client.maxVertexAttribBindPoints}" } + + if (index in boundBuffers) { + glVertexArrayVertexBuffer(pointer, index, 0, 0L, 0) + checkForGLError() + boundBuffers.remove(index) + } + + return this + } + + fun bindAttributeToIndex(attrib: Int, index: Int): VertexArrayObject { + client.ensureSameThread() + + require(attrib >= 0) { "Invalid attribute index: $attrib" } + require(index >= 0) { "Invalid bind index: $index" } + require(index < client.maxVertexAttribBindPoints) { "Bind index is too big: $index; Max ${client.maxVertexAttribBindPoints}" } + + glVertexArrayAttribBinding(pointer, attrib, index) + checkForGLError() + + return this + } + + fun attributeFormat(attrib: Int, type: GLType, normalized: Boolean, relativeOffset: Int): VertexArrayObject { + client.ensureSameThread() + require(attrib >= 0) { "Invalid attribute index: $attrib" } + + glVertexArrayAttribFormat(pointer, attrib, type.elementSize, type.elementType, normalized, relativeOffset) + checkForGLError() + + return this + } + + fun bindAttributes(buffer: BufferObject.VBO, attributes: VertexAttributes, bufferOffset: Long = 0L) { + bindBufferToIndex(buffer, 0, attributes.vertexStride, bufferOffset) + + for (attr in attributes.attributeList) { + bindAttributeToIndex(attr.index, 0) + attributeFormat(attr.index, attr.type.type, false, attr.relativeOffset) + enableAttribute(attr.index) + } + } + fun enableAttribute(position: Int): VertexArrayObject { client.ensureSameThread() - glEnableVertexArrayAttrib(pointer, position) - //glEnableVertexAttribArray(position) - checkForGLError() + + if (!enabledAttributes[position]) { + glEnableVertexArrayAttrib(pointer, position) + checkForGLError() + enabledAttributes[position] = true + } + + return this + } + + fun disableAttribute(position: Int): VertexArrayObject { + client.ensureSameThread() + + if (enabledAttributes[position]) { + glDisableVertexArrayAttrib(pointer, position) + checkForGLError() + enabledAttributes[position] = false + } + return this } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt index af6458cb..a88906b2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.client.gl.shader +import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL20.GL_COMPILE_STATUS import org.lwjgl.opengl.GL20.glCompileShader import org.lwjgl.opengl.GL20.glCreateShader @@ -33,9 +34,20 @@ class GLShader(body: String, type: Int) { glGetShaderiv(pointer, GL_COMPILE_STATUS, result) if (result[0] == 0) { + val split = body.split("\n") + val reps = if (split.size < 10) 1 else if (split.size < 100) 2 else if (split.size < 1000) 3 else 4 + + LOGGER.fatal("Next shader source has failed to compile, with line numbers:\n${split.withIndex().map { + "${it.index}${" ".repeat((reps - it.index.toString().length).coerceAtLeast(0))}: ${it.value}" + }.joinToString("\n")}") + throw ShaderCompilationException(glGetShaderInfoLog(pointer)) } checkForGLError() } + + companion object { + private val LOGGER = LogManager.getLogger() + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt index d59d6836..a6002a88 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt @@ -8,7 +8,7 @@ import org.lwjgl.opengl.GL45.* import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.GLObject import ru.dbotthepony.kstarbound.client.gl.checkForGLError -import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes import ru.dbotthepony.kvector.api.IStruct2f import ru.dbotthepony.kvector.api.IStruct3f import ru.dbotthepony.kvector.api.IStruct4f @@ -26,8 +26,15 @@ import kotlin.reflect.KProperty open class GLShaderProgram( shaders: Iterable, - val attributes: GLAttributeList + val attributes: VertexAttributes ) : GLObject() { + interface Regular { + var viewMatrix: Matrix3f // set before rendering anything (camera and/or projection) + var worldMatrix: Matrix3f // global matrix stack + var modelMatrix: Matrix3f // should be set by drawing code itself + var colorMultiplier: IStruct4f + } + final override val client = StarboundClient.current() final override val pointer = glCreateProgram() @@ -42,14 +49,12 @@ open class GLShaderProgram( glLinkProgram(pointer) - val success = intArrayOf(0) - glGetProgramiv(pointer, GL_LINK_STATUS, success) - - if (success[0] == 0) { + if (glGetProgrami(pointer, GL_LINK_STATUS) == 0) throw ShaderLinkException(glGetProgramInfoLog(pointer)) - } glGetError() + + client.addShaderProgram(this) } fun use(): GLShaderProgram { @@ -82,16 +87,17 @@ open class GLShaderProgram( return locations.computeIfAbsent(name, Object2ObjectFunction { IUniform(name) }) as? IUniform ?: throw IllegalStateException("Uniform $name has type of ${locations[name]!!::class.simpleName}") } - abstract inner class Uniform(val name: String) : ReadWriteProperty { + abstract inner class Uniform(val name: String, allowMissing: Boolean = false) : ReadWriteProperty { init { client.ensureSameThread() require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" } } val location = glGetUniformLocation(pointer, name) + val isMissing = location == -1 init { - if (location == -1) + if (isMissing && !allowMissing) throw NoSuchElementException("Program ${this@GLShaderProgram} does not have uniform with name $name") locations[name] = this @@ -113,10 +119,11 @@ open class GLShaderProgram( } } - inner class FUniform(name: String) : Uniform(name) { + inner class FUniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { override var value: Float = 0f set(value) { client.ensureSameThread() + if (isMissing) return if (field != value) { glProgramUniform1f(pointer, location, value) @@ -135,13 +142,14 @@ open class GLShaderProgram( } } - inner class F2Uniform(name: String) : Uniform(name) { + inner class F2Uniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { private var v0 = 0f private var v1 = 0f override var value: IStruct2f = Vector2f.ZERO set(value) { client.ensureSameThread() + if (isMissing) return val (v0, v1) = value @@ -156,7 +164,7 @@ open class GLShaderProgram( } } - inner class F3Uniform(name: String) : Uniform(name) { + inner class F3Uniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { private var v0 = 0f private var v1 = 0f private var v2 = 0f @@ -164,6 +172,7 @@ open class GLShaderProgram( override var value: IStruct3f = Vector3f.ZERO set(value) { client.ensureSameThread() + if (isMissing) return val (v0, v1, v2) = value @@ -182,10 +191,11 @@ open class GLShaderProgram( private val buff3x3: FloatBuffer = ByteBuffer.allocateDirect(4 * 3 * 3).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer() private val buff4x4: FloatBuffer = ByteBuffer.allocateDirect(4 * 4 * 4).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer() - inner class F3x3Uniform(name: String) : Uniform(name) { + inner class F3x3Uniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { override var value: Matrix3f = Matrix3f.zero() set(value) { client.ensureSameThread() + if (isMissing) return if (field != value) { buff3x3.position(0) @@ -199,15 +209,16 @@ open class GLShaderProgram( } } - inner class F4x4Uniform(name: String) : Uniform(name) { + inner class F4x4Uniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { private val _value = ByteBuffer.allocate(4 * 4 * 4).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer() override var value: Matrix4f = Matrix4f.zero() set(value) { client.ensureSameThread() + if (isMissing) return buff4x4.position(0) - value.storeRowColumn(buff4x4) + value.storeColumnRow(buff4x4) buff4x4.position(0) _value.position(0) @@ -221,7 +232,7 @@ open class GLShaderProgram( } } - inner class F4Uniform(name: String) : Uniform(name) { + inner class F4Uniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { private var v0 = 0f private var v1 = 0f private var v2 = 0f @@ -230,6 +241,7 @@ open class GLShaderProgram( override var value: IStruct4f = Vector4f.ZERO set(value) { client.ensureSameThread() + if (isMissing) return val (v0, v1, v2, v3) = value @@ -246,10 +258,11 @@ open class GLShaderProgram( } } - inner class IUniform(name: String) : Uniform(name) { + inner class IUniform(name: String, allowMissing: Boolean = false) : Uniform(name, allowMissing) { override var value: Int = 0 set(value) { client.ensureSameThread() + if (isMissing) return if (field != value) { glProgramUniform1i(pointer, location, value) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt index a72619af..a79d815c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt @@ -1,125 +1,50 @@ package ru.dbotthepony.kstarbound.client.gl.shader import ru.dbotthepony.kstarbound.client.StarboundClient -import ru.dbotthepony.kstarbound.client.gl.GLType -import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList -import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType -import ru.dbotthepony.kvector.arrays.Matrix4f +import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes +import ru.dbotthepony.kvector.api.IStruct4f +import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor -private fun internalVertex(string: String) = StarboundClient.current().internalVertex(string) -private fun internalFragment(string: String) = StarboundClient.current().internalFragment(string) - private fun shaders(name: String): List { val client = StarboundClient.current() return listOf(client.internalVertex("shaders/$name.vsh"), client.internalFragment("shaders/$name.fsh")) } -class GLLiquidProgram : GLShaderProgram(shaders("liquid"), FORMAT) { +private fun shaders1(name: String): List { + val client = StarboundClient.current() + return listOf(client.internalVertex("shaders/vertex/$name.vsh"), client.internalFragment("shaders/fragment/$name.fsh")) +} + +class FontProgram : GLShaderProgram(shaders1("font"), VertexAttributes.POSITION_UV), GLShaderProgram.Regular { + override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix", true) + override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix", true) + override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix", true) + override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier", true) + var texture by IUniform("texture0") + + val builder = StreamVertexBuilder(attributes, GeometryType.QUADS) + + init { + viewMatrix = Matrix3f.identity() + modelMatrix = Matrix3f.identity() + colorMultiplier = RGBAColor.WHITE + } +} + +class GLLiquidProgram : GLShaderProgram(shaders("liquid"), VertexAttributes.POSITION) { var baselineColor by F4Uniform("baselineColor") - var transform by F4x4Uniform("transform") - - val builder by lazy { - StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384) - } - - companion object { - val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build() - } -} - -class GLFlatColorProgram : GLShaderProgram(shaders("flat_color"), FORMAT) { - var transform by F4x4Uniform("transform") - - val builder by lazy { - StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384) - } - - companion object { - val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC4F).build() - } -} - -class GLTileProgram : GLShaderProgram(shaders("tile"), FORMAT) { - var transform by F4x4Uniform("transform") - var color by F4Uniform("color") - var texture by IUniform("texture0") - - init { - transform = Matrix4f.identity() - color = RGBAColor.WHITE - } - - companion object { - val FORMAT = GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F, GLType.FLOAT).build() - } -} - -class GLFontProgram : GLShaderProgram(shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) { - var transform by F4x4Uniform("transform") - var color by F4Uniform("color") - var texture by IUniform("texture0") - - init { - transform = Matrix4f.identity() - color = RGBAColor.WHITE - } -} - -class GLFlatProgram : GLShaderProgram(shaders("flat"), GLAttributeList.VEC2F) { - var transform by F4x4Uniform("transform") - var color by F4Uniform("color") - - init { - color = RGBAColor.WHITE - } -} - -class GLTexturedProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/texture.glsl"), internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) { - var transform by F4x4Uniform("transform") - var texture by IUniform("texture0") - - val builder by lazy { - StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) - } - - init { - transform = Matrix4f.identity() - } -} - -class GLTextured2dProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/2dtexture.glsl"), internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) { - var transform by F4x4Uniform("transform") - var texture by IUniform("texture0") - - val builder by lazy { - StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC2F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) - } - - init { - transform = Matrix4f.identity() - } -} - -class GLTexturedColoredProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/texture.glsl"), internalFragment("shaders/fragment/texture_color.glsl")), GLAttributeList.VERTEX_TEXTURE) { - var transform by F4x4Uniform("transform") - var texture by IUniform("texture0") - var color by F4Uniform("color") - - init { - transform = Matrix4f.identity() - color = RGBAColor.WHITE - } + var transform by F3x3Uniform("transform") + val builder = StreamVertexBuilder(attributes, GeometryType.QUADS) } class GLPrograms { - val tile = GLTileProgram() - val font = GLFontProgram() - val flat = GLFlatProgram() - val flatColor = GLFlatColorProgram() + val position = UberShader.Builder().build() + val positionTexture = UberShader.Builder().withTexture().build() + val positionColor = UberShader.Builder().withColor().build() + val tile = UberShader.Builder().withTexture().withHueShift().build() + val font = FontProgram() val liquid = GLLiquidProgram() - val textured = GLTexturedProgram() - val textured2d = GLTextured2dProgram() - val texturedColored = GLTexturedColoredProgram() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt new file mode 100644 index 00000000..33e28fe0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt @@ -0,0 +1,84 @@ +package ru.dbotthepony.kstarbound.client.gl.shader + +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER +import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributeType +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes +import ru.dbotthepony.kvector.api.IStruct4f +import ru.dbotthepony.kvector.arrays.Matrix3f +import ru.dbotthepony.kvector.vector.RGBAColor + +class UberShader private constructor(fragment: GLShader, vertex: GLShader, attributes: VertexAttributes) : GLShaderProgram(listOf(fragment, vertex), attributes), GLShaderProgram.Regular { + override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix") + override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix") + override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix") + override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier") + + var texture0 by IUniform("texture0", VertexAttributeType.UV !in attributes) + + val builder = StreamVertexBuilder(attributes) + + init { + viewMatrix = Matrix3f.identity() + worldMatrix = Matrix3f.identity() + modelMatrix = Matrix3f.identity() + colorMultiplier = RGBAColor.WHITE + } + + class Builder { + private val directives = Object2ObjectArrayMap() + private val attributes = ObjectArraySet() + + init { + attributes.add(VertexAttributeType.POSITION) + } + + private var nextAttributeIndex = 1 + + private fun attribute(type: VertexAttributeType, name: String = type.name): Builder { + if (attributes.add(type)) { + directives[name] = nextAttributeIndex.toString() + nextAttributeIndex += type.type.width + } + + return this + } + + fun withTexture(): Builder { + return attribute(VertexAttributeType.UV, "TEXTURE") + } + + fun withColor(): Builder { + return attribute(VertexAttributeType.COLOR) + } + + fun withHueShift(): Builder { + return attribute(VertexAttributeType.HUE_SHIFT) + } + + fun build(): UberShader { + val client = StarboundClient.current() + + val fragment = GLShader("#version 450\n" + + directives.entries.joinToString("\n") { "#define ${it.key}" + (if (it.value != "") " " + it.value else "") } + + fragment, GL_FRAGMENT_SHADER) + + val vertex = GLShader("#version 450\n" + + directives.entries.joinToString("\n") { "#define ${it.key}" + (if (it.value != "") " " + it.value else "") } + + vertex, GL_VERTEX_SHADER) + + val attributes = VertexAttributes.of(attributes) + + return UberShader(fragment, vertex, attributes) + } + } + + companion object { + private val fragment by lazy { StarboundClient.readInternal("shaders/fragment/configurable.fsh") } + private val vertex by lazy { StarboundClient.readInternal("shaders/vertex/configurable.vsh") } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GLAttributeList.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GLAttributeList.kt deleted file mode 100644 index 7cee4cab..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GLAttributeList.kt +++ /dev/null @@ -1,89 +0,0 @@ -package ru.dbotthepony.kstarbound.client.gl.vertex - -import com.google.common.collect.ImmutableList -import ru.dbotthepony.kstarbound.client.gl.GLType -import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject - - -/** - * Хранит список аттрибутов для применения к Vertex Array Object - * - * Аттрибуты плотно упакованы и идут один за другим - * - * Создаётся через [GLAttributeList.Builder] - */ -class GLAttributeList(builder: Builder) { - data class Attribute(val name: String, val index: Int, val glType: GLType, val stride: Int, val offset: Long) - - val attributes: List - val size get() = attributes.size - - /** - * Шаг данных аттрибутов, в байтах. Т.е. одна полная вершина будет занимать [stride] байт в памяти. - */ - val stride: Int - - operator fun get(index: Int) = attributes[index] - - init { - val buildList = ArrayList() - - var offset = 0L - var stride = 0 - - for (i in builder.attributes) { - stride += i.second.byteSize - } - - this.stride = stride - - for (i in builder.attributes.indices) { - val value = builder.attributes[i].second - buildList.add(Attribute(builder.attributes[i].first, i, value, stride, offset)) - offset += value.byteSize - } - - attributes = ImmutableList.copyOf(buildList) - } - - /** - * Применяет список атрибутов к заданному [VertexArrayObject] (попутно включая или отключая их через [enable]) - */ - fun apply(target: VertexArrayObject, enable: Boolean) { - for (i in attributes.indices) { - val value = attributes[i] - target.attribute(i, value.glType.logicalSize, value.glType.typeIndentity, false, value.stride, value.offset) - - if (enable) { - target.enableAttribute(i) - } - } - } - - class Builder { - val attributes = ArrayList>() - - fun push(type: GLType): Builder { - return push("$type#${attributes.size}", type) - } - - fun push(vararg types: GLType): Builder { - for (type in types) push(type) - return this - } - - fun push(name: String, type: GLType): Builder { - check(!attributes.any { it.first == name }) { "Already has named attribute $name!" } - attributes.add(name to type) - return this - } - - fun build() = GLAttributeList(this) - } - - companion object { - val VEC2F = Builder().push(GLType.VEC2F).build() - val VERTEX_TEXTURE = Builder().push(GLType.VEC3F).push(GLType.VEC2F).build() - val VERTEX_2D_TEXTURE = Builder().push(GLType.VEC2F).push(GLType.VEC2F).build() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt index 40c0aa48..d2fb8e08 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt @@ -17,15 +17,10 @@ enum class GeometryType( LINES(2, IntList.of(0, 1)), TRIANGLES(3, IntList.of(0, 1, 2)), - /** - * A B C B C D - */ - QUADS(4, IntList.of(0, 1, 2, 1, 2, 3)), - /** * A B C C D A */ - QUADS_ALTERNATIVE(4, IntList.of(0, 1, 2, 2, 3, 0)), + QUADS(4, IntList.of(0, 1, 2, 2, 3, 0)), QUADS_AS_LINES(4, IntList.of(0, 1, 0, 2, 1, 3, 2, 3)), QUADS_AS_LINES_WIREFRAME(4, IntList.of(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)), diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/QuadTransformers.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/QuadTransformers.kt deleted file mode 100644 index e58a90a3..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/QuadTransformers.kt +++ /dev/null @@ -1,71 +0,0 @@ -package ru.dbotthepony.kstarbound.client.gl.vertex - -import ru.dbotthepony.kstarbound.defs.image.IUVCoordinates -import ru.dbotthepony.kstarbound.defs.image.UVCoordinates -import ru.dbotthepony.kvector.vector.RGBAColor - -typealias QuadVertexTransformer = (VertexBuilder, Int) -> VertexBuilder - -val EMPTY_VERTEX_TRANSFORM: QuadVertexTransformer = { it, _ -> it } - -fun QuadVertexTransformer.before(other: QuadVertexTransformer): QuadVertexTransformer { - return { a, b -> - other.invoke(a, b) - this.invoke(a, b) - } -} - -fun QuadVertexTransformer.after(other: QuadVertexTransformer): QuadVertexTransformer { - return { a, b -> - this.invoke(a, b) - other.invoke(a, b) - } -} - -object QuadTransformers { - fun uv(u0: Float, - v0: Float, - u1: Float, - v1: Float, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): QuadVertexTransformer { - return transformer@{ it, index -> - when (index) { - 0 -> it.pushVec2f(u0, v0) - 1 -> it.pushVec2f(u1, v0) - 2 -> it.pushVec2f(u0, v1) - 3 -> it.pushVec2f(u1, v1) - } - - return@transformer lambda(it, index) - } - } - - fun uv(): QuadVertexTransformer { - return transformer@{ it, index -> - when (index) { - 0 -> it.pushVec2f(0f, 0f) - 1 -> it.pushVec2f(1f, 0f) - 2 -> it.pushVec2f(0f, 1f) - 3 -> it.pushVec2f(1f, 1f) - } - - return@transformer it - } - } - - fun uv(uv: IUVCoordinates): QuadVertexTransformer { - return uv(uv.u0, uv.v0, uv.u1, uv.v1) - } - - fun uv(uv: IUVCoordinates, lambda: QuadVertexTransformer): QuadVertexTransformer { - return uv(uv.u0, uv.v0, uv.u1, uv.v1, lambda) - } - - fun vec4(x: Float, y: Float, z: Float, w: Float, after: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): QuadVertexTransformer { - return transformer@{ it, index -> - it.pushVec4f(x, y, z, w) - return@transformer after(it, index) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt index 03fe8181..d687aa04 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt @@ -10,8 +10,8 @@ import ru.dbotthepony.kstarbound.client.gl.checkForGLError * Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка */ class StreamVertexBuilder( - attributes: GLAttributeList, - type: GeometryType, + attributes: VertexAttributes, + type: GeometryType? = null, initialCapacity: Int = 64, ) { val state = StarboundClient.current() @@ -21,15 +21,8 @@ class StreamVertexBuilder( private val ebo = BufferObject.EBO() init { - vao.bind() - vbo.bind() - ebo.bind() - - attributes.apply(vao, true) - - vao.unbind() - vbo.unbind() - ebo.unbind() + vao.elementBuffer = ebo + vao.bindAttributes(vbo, attributes) } fun upload(drawType: Int = GL45.GL_DYNAMIC_DRAW) { @@ -43,22 +36,6 @@ class StreamVertexBuilder( bind() GL45.glDrawElements(primitives, builder.indexCount, builder.indexType, 0L) checkForGLError() - } - - fun singleSprite(x: Float, y: Float, width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) { - builder.begin() - - builder.quadRotatedZ(x, y, width, height, z, 0f, 0f, angle, transformer) - - upload() - draw() - } - - fun singleSprite(width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) { - singleSprite(-width / 2f, -height / 2f, width / 2f, height / 2f, z, angle, transformer) - } - - fun singleSprite(width: Float, height: Float, angle: Double = 0.0, transformer: QuadVertexTransformer) { - singleSprite(-width / 2f, -height / 2f, width / 2f, height / 2f, 0f, angle, transformer) + unbind() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexAttributeType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexAttributeType.kt new file mode 100644 index 00000000..52a41b39 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexAttributeType.kt @@ -0,0 +1,10 @@ +package ru.dbotthepony.kstarbound.client.gl.vertex + +import ru.dbotthepony.kstarbound.client.gl.GLType + +enum class VertexAttributeType(val type: GLType) { + POSITION(GLType.VEC2F), + COLOR(GLType.VEC4F), + UV(GLType.VEC2F), + HUE_SHIFT(GLType.FLOAT) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexAttributes.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexAttributes.kt new file mode 100644 index 00000000..d0b16a73 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexAttributes.kt @@ -0,0 +1,70 @@ +package ru.dbotthepony.kstarbound.client.gl.vertex + +import com.google.common.collect.ImmutableList +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import java.util.Collections +import java.util.EnumMap +import java.util.EnumSet + +class VertexAttributes private constructor( + val attributeList: ImmutableList, + val attributeMap: Map, + val vertexStride: Int, + val allFlags: Long, +) { + data class Attribute(val type: VertexAttributeType, val index: Int, val relativeOffset: Int, val flag: Long) + + val size get() = attributeList.size + operator fun get(index: Int): Attribute = attributeList[index] + operator fun get(index: VertexAttributeType): Attribute? = attributeMap[index] + + operator fun contains(index: VertexAttributeType) = index in attributeMap + + companion object { + val POSITION = of(VertexAttributeType.POSITION) + val POSITION_UV = of(VertexAttributeType.POSITION, VertexAttributeType.UV) + val POSITION_COLOR = of(VertexAttributeType.POSITION, VertexAttributeType.COLOR) + + @JvmStatic + fun of(vararg attributes: VertexAttributeType): VertexAttributes { + return of(ObjectArrayList.wrap(attributes)) + } + + @JvmStatic + fun of(attributes: Collection): VertexAttributes { + val dup = EnumSet.noneOf(VertexAttributeType::class.java) + + attributes.forEach { + if (!dup.add(it)) { + throw IllegalArgumentException("Duplicate vertex attribute: $it") + } + } + + require(attributes.size < 64) { "Vertex attribute list is insanely big: ${attributes.size}; 64 is max" } + + val attributeList = ImmutableList.Builder() + val attributeMap = EnumMap(VertexAttributeType::class.java) + var allVertexFlags = 0L + + var stride = 0 + + for (attr in attributes) { + stride += attr.type.byteSize + allVertexFlags = (allVertexFlags shl 1) or 1L + } + + var offset = 0 + var index = 0 + + for ((i, attr) in attributes.withIndex()) { + val constructed = Attribute(attr, index, offset, 1L shl i) + index += attr.type.width + attributeList.add(constructed) + offset += attr.type.byteSize + attributeMap[attr] = constructed + } + + return VertexAttributes(attributeList.build(), Collections.unmodifiableMap(attributeMap), stride, allVertexFlags) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexBuilder.kt index 0ec04554..a0792034 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexBuilder.kt @@ -4,13 +4,11 @@ import org.lwjgl.opengl.GL45 import org.lwjgl.opengl.GL45.GL_UNSIGNED_INT import org.lwjgl.opengl.GL45.GL_UNSIGNED_SHORT import org.lwjgl.opengl.GL45.GL_UNSIGNED_BYTE -import ru.dbotthepony.kstarbound.client.gl.GLType import ru.dbotthepony.kstarbound.client.gl.BufferObject -import ru.dbotthepony.kvector.util2d.AABB +import ru.dbotthepony.kvector.api.IStruct2f +import ru.dbotthepony.kvector.api.IStruct4f import java.nio.ByteBuffer import java.nio.ByteOrder -import kotlin.math.cos -import kotlin.math.sin private fun interface IndexWriter { fun write(buffer: ByteBuffer, value: Int) @@ -50,11 +48,11 @@ private fun indexSize(type: Int): Int { * Загрузка в память видеокарты происходит напрямую из буферов, через метод [upload] */ class VertexBuilder( - val attributes: GLAttributeList, + val attributes: VertexAttributes, val defaultMode: GeometryType? = null, initialCapacity: Int = 64 ) { - constructor(attributes: GLAttributeList, initialCapacity: Int) : this(attributes, null, initialCapacity) + constructor(attributes: VertexAttributes, initialCapacity: Int) : this(attributes, null, initialCapacity) init { require(initialCapacity > 0) { "Invalid capacity: $initialCapacity" } @@ -73,14 +71,13 @@ class VertexBuilder( var indexSize: Int = indexSize(indexType) private set - private var vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.stride).also { it.order(ByteOrder.LITTLE_ENDIAN) } - private var indexMemory = ByteBuffer.allocateDirect(indexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) } + private var vertexMemory: ByteBuffer = ByteBuffer.allocateDirect(vertexCapacity * attributes.vertexStride).also { it.order(ByteOrder.LITTLE_ENDIAN) } + private var indexMemory: ByteBuffer = ByteBuffer.allocateDirect(indexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) } private var indexWriter = writer(indexType) private var inVertex = false - private var attributeIndex = 0 + private var vertexAttributes = 0L private var elementIndexOffset = 0 - private var elementVertices = 0 var vertexCount = 0 // distinct vertices @@ -113,7 +110,7 @@ class VertexBuilder( val indexSize = indexSize(indexType) val vertexPos = vertexMemory.position() - val vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.stride).also { it.order(ByteOrder.LITTLE_ENDIAN) } + val vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.vertexStride).also { it.order(ByteOrder.LITTLE_ENDIAN) } this.vertexMemory.position(0) vertexMemory.put(this.vertexMemory) @@ -171,7 +168,13 @@ class VertexBuilder( } fun mode(mode: GeometryType): VertexBuilder { - check(!inVertex) { "Can't change buffer geometry type during vertex construction" } + if (inVertex) { + if (vertexAttributes != attributes.allFlags) + throw IllegalStateException("Can't change buffer geometry type during vertex construction") + else + end() + } + check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" } this.mode = mode ensureCapacity() @@ -183,8 +186,8 @@ class VertexBuilder( fun begin(mode: GeometryType? = defaultMode): VertexBuilder { this.mode = mode inVertex = false - attributeIndex = 0 elementIndexOffset = 0 + vertexAttributes = 0L vertexCount = 0 indexCount = 0 elementCount = 0 @@ -194,33 +197,7 @@ class VertexBuilder( return this } - private fun checkBounds() { - if (attributeIndex >= attributes.size) { - throw IndexOutOfBoundsException("Tried to add new attribute when already added all attributes") - } - } - - fun expect(type: GLType): VertexBuilder { - checkBounds() - - if (attributes[attributeIndex].glType != type) { - throw IllegalStateException("Expected attribute type $type, got ${attributes[attributeIndex].name}[${attributes[attributeIndex].glType}] (at position $attributeIndex)") - } - - return this - } - - fun expect(name: String): VertexBuilder { - checkBounds() - - if (attributes[attributeIndex].name != name) { - throw IllegalStateException("Expected attribute name $name, got ${attributes[attributeIndex].name}[${attributes[attributeIndex].glType}] (at position $attributeIndex)") - } - - return this - } - - fun upload(vbo: BufferObject.VBO, ebo: BufferObject.EBO, drawType: Int = GL45.GL_DYNAMIC_DRAW) { + fun upload(vertices: BufferObject.VBO, elements: BufferObject.EBO, drawType: Int = GL45.GL_DYNAMIC_DRAW) { end() check(elementVertices == 0) { "Not fully built vertex element ($mode requires ${mode?.elements} vertex points to be present, yet last strip has only $elementVertices elements)" } @@ -231,8 +208,8 @@ class VertexBuilder( vertexMemory.position(0) indexMemory.position(0) - vbo.bufferData(vertexMemory, drawType, length = vertexPos.toLong()) - ebo.bufferData(indexMemory, drawType, length = elementPos.toLong()) + vertices.bufferData(vertexMemory, drawType, length = vertexPos.toLong()) + elements.bufferData(indexMemory, drawType, length = elementPos.toLong()) vertexMemory.position(vertexPos) indexMemory.position(elementPos) @@ -244,8 +221,11 @@ class VertexBuilder( val mode = mode!! inVertex = false - if (attributeIndex != attributes.size) { - throw IllegalStateException("Unfinished vertex, we are at $attributeIndex, while we have ${attributes.size} attributes") + if (vertexAttributes != attributes.allFlags) { + val missing = ArrayList() + attributes.attributeList.forEach { if (it.flag and vertexAttributes == 0L) missing.add(it.type) } + + throw IllegalStateException("Unfinished vertex, missing ${missing.joinToString()} attributes") } vertexCount++ @@ -271,171 +251,63 @@ class VertexBuilder( checkNotNull(mode) { "No builder mode is set" } inVertex = true - attributeIndex = 0 + vertexAttributes = 0L return this } - fun pushVec4f(x: Float, y: Float, z: Float, w: Float): VertexBuilder { - expect(GLType.VEC4F) - val memory = vertexMemory - memory.putFloat(x) - memory.putFloat(y) - memory.putFloat(z) - memory.putFloat(w) - attributeIndex++ - return this - } - - fun pushVec3f(x: Float, y: Float, z: Float): VertexBuilder { - expect(GLType.VEC3F) - val memory = vertexMemory - memory.putFloat(x) - memory.putFloat(y) - memory.putFloat(z) - attributeIndex++ - return this - } - - fun pushVec2f(x: Float, y: Float): VertexBuilder { - expect(GLType.VEC2F) - val memory = vertexMemory - memory.putFloat(x) - memory.putFloat(y) - attributeIndex++ - return this - } - - fun push(value: Float): VertexBuilder { - expect(GLType.FLOAT) - vertexMemory.putFloat(value) - attributeIndex++ - return this - } - - fun shadowLine(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder { + fun vertex(x: Float, y: Float): VertexBuilder { vertex() - pushVec4f(x0, y0, x1, y1) - pushVec2f(0f, 0f) + position(x, y) + return this + } - vertex() - pushVec4f(x0, y0, x1, y1) - pushVec2f(0f, 1f) - - vertex() - pushVec4f(x0, y0, x1, y1) - pushVec2f(1f, 1f) - - vertex() - pushVec4f(x0, y0, x1, y1) - pushVec2f(1f, 0f) + private fun pushFloat(attr: VertexAttributes.Attribute, x: Float): VertexBuilder { + vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset) + vertexMemory.putFloat(x) + vertexAttributes = vertexAttributes or attr.flag return this } - fun dfShadowLine(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder { - shadowLine(x0, y0, x1, y1) - shadowLine(x1, y1, x0, y0) - return this - } - - fun shadowQuad(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder { - shadowLine(x0, y0, x1, y0) - shadowLine(x1, y0, x1, y1) - shadowLine(x1, y1, x0, y1) - shadowLine(x0, y1, x0, y0) - return this - } - - // Помощники - fun quad( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): VertexBuilder { - check(mode?.elements == 4) { "Currently building $mode" } - - lambda(vertex().pushVec2f(x0, y0), 0).end() - lambda(vertex().pushVec2f(x1, y0), 1).end() - lambda(vertex().pushVec2f(x0, y1), 2).end() - lambda(vertex().pushVec2f(x1, y1), 3).end() + private fun pushFloat2(attr: VertexAttributes.Attribute, x: Float, y: Float): VertexBuilder { + vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset) + vertexMemory.putFloat(x) + vertexMemory.putFloat(y) + vertexAttributes = vertexAttributes or attr.flag return this } - fun quadRotated( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - x: Float, - y: Float, - angle: Double, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): VertexBuilder { - check(mode?.elements == 4) { "Currently building $mode" } - - val s = sin(angle).toFloat() - val c = cos(angle).toFloat() - - lambda(vertex().pushVec2f(x + x0 * c - s * y0, y + s * x0 + c * y0), 0).end() - lambda(vertex().pushVec2f(x + x1 * c - s * y0, y + s * x1 + c * y0), 1).end() - lambda(vertex().pushVec2f(x + x0 * c - s * y1, y + s * x0 + c * y1), 2).end() - lambda(vertex().pushVec2f(x + x1 * c - s * y1, y + s * x1 + c * y1), 3).end() + private fun pushFloat4(attr: VertexAttributes.Attribute, x: Float, y: Float, z: Float, w: Float): VertexBuilder { + vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset) + vertexMemory.putFloat(x) + vertexMemory.putFloat(y) + vertexMemory.putFloat(z) + vertexMemory.putFloat(w) + vertexAttributes = vertexAttributes or attr.flag return this } - fun quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): VertexBuilder { - return quad( - aabb.mins.x.toFloat(), - aabb.mins.y.toFloat(), - aabb.maxs.x.toFloat(), - aabb.maxs.y.toFloat(), - lambda - ) + fun position(x: Float, y: Float): VertexBuilder { + return pushFloat2(requireNotNull(attributes[VertexAttributeType.POSITION]) { "Vertex format does not have position attribute" }, x, y) } - fun quadZ( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - z: Float, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): VertexBuilder { - check(mode?.elements == 4) { "Currently building $mode" } + fun position(value: IStruct2f) = position(value.component1(), value.component2()) - lambda(vertex().pushVec3f(x0, y0, z), 0).end() - lambda(vertex().pushVec3f(x1, y0, z), 1).end() - lambda(vertex().pushVec3f(x0, y1, z), 2).end() - lambda(vertex().pushVec3f(x1, y1, z), 3).end() - - return this + fun uv(x: Float, y: Float): VertexBuilder { + return pushFloat2(requireNotNull(attributes[VertexAttributeType.UV]) { "Vertex format does not have texture UV attribute" }, x, y) } - fun quadRotatedZ( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - z: Float, - x: Float, - y: Float, - angle: Double, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): VertexBuilder { - check(mode?.elements == 4) { "Currently building $mode" } + fun uv(value: IStruct2f) = position(value.component1(), value.component2()) - val s = sin(angle).toFloat() - val c = cos(angle).toFloat() + fun color(x: Float, y: Float, z: Float, w: Float): VertexBuilder { + return pushFloat4(requireNotNull(attributes[VertexAttributeType.COLOR]) { "Vertex format does not have color attribute" }, x, y, z, w) + } - lambda(vertex().pushVec3f(x + x0 * c - s * y0, y + s * x0 + c * y0, z), 0).end() - lambda(vertex().pushVec3f(x + x1 * c - s * y0, y + s * x1 + c * y0, z), 1).end() - lambda(vertex().pushVec3f(x + x0 * c - s * y1, y + s * x0 + c * y1, z), 2).end() - lambda(vertex().pushVec3f(x + x1 * c - s * y1, y + s * x1 + c * y1, z), 3).end() + fun color(value: IStruct4f) = color(value.component1(), value.component2(), value.component3(), value.component4()) - return this + fun hueShift(x: Float): VertexBuilder { + return pushFloat(requireNotNull(attributes[VertexAttributeType.HUE_SHIFT]) { "Vertex format does not have texture UV attribute" }, x) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt index eb78e65d..5479bac1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt @@ -5,6 +5,10 @@ import ru.dbotthepony.kbox2d.api.IDebugDraw import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType +import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes +import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2d import kotlin.math.cos @@ -12,6 +16,8 @@ import kotlin.math.sin class Box2DRenderer : IDebugDraw { val state = StarboundClient.current() + private val identity = Matrix3f.identity() + override var drawShapes: Boolean = false override var drawJoints: Boolean = false override var drawAABB: Boolean = false @@ -19,25 +25,26 @@ class Box2DRenderer : IDebugDraw { override var drawPairs: Boolean = false override var drawCenterOfMess: Boolean = false + private val builder = StreamVertexBuilder(VertexAttributes.POSITION) + override fun drawPolygon(vertices: List, color: RGBAColor) { require(vertices.size > 1) { "Vertex list had only ${vertices.size} namings in it" } - val builder = state.flat2DLines - builder.builder.begin() for (i in vertices.indices) { val current = vertices[i] val next = vertices[(i + 1) % vertices.size] - builder.builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat()) - builder.builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat()) + builder.builder.vertex(current.x.toFloat(), current.y.toFloat()) + builder.builder.vertex(next.x.toFloat(), next.y.toFloat()) } builder.upload() - state.programs.flat.use() - state.programs.flat.color = color - state.programs.flat.transform = state.matrixStack.last() + state.programs.position.use() + state.programs.position.colorMultiplier = color + state.programs.position.worldMatrix = state.stack.last() + state.programs.position.modelMatrix = identity builder.draw(GL_LINES) } @@ -45,25 +52,24 @@ class Box2DRenderer : IDebugDraw { private fun drawSolid(vertices: List, color: RGBAColor) { require(vertices.size >= 3) { "Vertex list had only ${vertices.size} namings in it" } - val builder = state.flat2DTriangles - - builder.builder.begin() + builder.builder.begin(GeometryType.TRIANGLES) val zero = vertices[0] for (i in 1 until vertices.size) { val current = vertices[i] val next = vertices[(i + 1) % vertices.size] - builder.builder.vertex().pushVec2f(zero.x.toFloat(), zero.y.toFloat()) - builder.builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat()) - builder.builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat()) + builder.builder.vertex(zero.x.toFloat(), zero.y.toFloat()) + builder.builder.vertex(current.x.toFloat(), current.y.toFloat()) + builder.builder.vertex(next.x.toFloat(), next.y.toFloat()) } builder.upload() - state.programs.flat.use() - state.programs.flat.color = color - state.programs.flat.transform = state.matrixStack.last() + state.programs.position.use() + state.programs.position.colorMultiplier = color + state.programs.position.worldMatrix = state.stack.last() + state.programs.position.modelMatrix = identity builder.draw(GL_TRIANGLES) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt index 147d3501..8209dd77 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt @@ -7,11 +7,10 @@ import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.freetype.LoadFlag import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode -import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList -import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder -import ru.dbotthepony.kvector.arrays.Matrix4fStack +import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor private fun breakLines(text: String): List { @@ -91,7 +90,6 @@ class Font( color: RGBAColor = RGBAColor.WHITE, scale: Float = 1f, - stack: Matrix4fStack = state.matrixStack, ): TextSize { if (text.isEmpty()) return TextSize(0f, 0f) @@ -102,20 +100,22 @@ class Font( val totalX = totalSize.width val totalY = totalSize.height - stack.push() + val model = Matrix3f.identity() when (alignY) { - TextAlignY.TOP -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale + y) - TextAlignY.CENTER -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale - totalY * scale / 2f + y) - TextAlignY.BOTTOM -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale - totalY * scale + y) + TextAlignY.TOP -> model.translate(x = x, y = lineHeight * scale + y) + TextAlignY.CENTER -> model.translate(x = x, y = lineHeight * scale - totalY * scale / 2f + y) + TextAlignY.BOTTOM -> model.translate(x = x, y = lineHeight * scale - totalY * scale + y) } if (scale != 1f) - stack.last().scale(x = scale, y = scale) + model.scale(x = scale, y = scale) state.programs.font.use() - state.programs.font.color = color + state.programs.font.colorMultiplier = color + state.programs.font.worldMatrix = state.stack.last() state.activeTexture = 0 + state.programs.font.texture = 0 val space = getGlyph(' ') @@ -123,7 +123,7 @@ class Font( for (line in text) { if (line == "") { - stack.last().translateWithMultiplication(y = lineHeight) + model.translate(y = lineHeight) continue } @@ -134,12 +134,12 @@ class Font( TextAlignX.CENTER -> { movedX = totalX / 2f - lineWidth(line, space) / 2f - stack.last().translateWithMultiplication(x = movedX) + model.translate(x = movedX) } TextAlignX.RIGHT -> { movedX = -lineWidth(line, space) - stack.last().translateWithMultiplication(x = movedX) + model.translate(x = movedX) } } @@ -150,10 +150,10 @@ class Font( if (chr == '\t') { if (lineGlyphs % 4 == 0) { advancedX += space.advanceX * 4 - stack.last().translateWithMultiplication(x = space.advanceX * 4) + model.translate(x = space.advanceX * 4) } else { advancedX += space.advanceX * (lineGlyphs % 4) - stack.last().translateWithMultiplication(x = space.advanceX * (lineGlyphs % 4)) + model.translate(x = space.advanceX * (lineGlyphs % 4)) } lineGlyphs += lineGlyphs % 4 @@ -161,17 +161,16 @@ class Font( } val glyph = getGlyph(chr) - glyph.render(stack) + glyph.render(model) lineWidth += glyph.advanceX lineGlyphs++ } advancedX = advancedX.coerceAtLeast(lineWidth) - stack.last().translateWithMultiplication(x = -lineWidth - movedX, y = lineHeight) + model.translate(x = -lineWidth - movedX, y = lineHeight) } state.vao = null - stack.pop() return TextSize(totalX * scale, totalY * scale) } @@ -188,7 +187,6 @@ class Font( color: RGBAColor = RGBAColor.WHITE, scale: Float = 1f, - stack: Matrix4fStack = state.matrixStack, ): TextSize { return render( breakLines(text), @@ -197,7 +195,6 @@ class Font( alignX = alignX, alignY = alignY, scale = scale, - stack = stack, color = color, ) } @@ -210,10 +207,10 @@ class Font( if (chr == '\t') { if (lineGlyphs % 4 == 0) { lineWidth += space.advanceX * 4 - state.matrixStack.last().translateWithMultiplication(x = space.advanceX * 4) + state.stack.last().translate(x = space.advanceX * 4) } else { lineWidth += space.advanceX * (lineGlyphs % 4) - state.matrixStack.last().translateWithMultiplication(x = space.advanceX * (lineGlyphs % 4)) + state.stack.last().translate(x = space.advanceX * (lineGlyphs % 4)) } lineGlyphs += lineGlyphs % 4 @@ -258,9 +255,7 @@ class Font( val advanceX: Float val advanceY: Float - private val vbo: BufferObject.VBO? // все три указателя должны хранится во избежание утечки - private val ebo: BufferObject.EBO? // все три указателя должны хранится во избежание утечки - private val vao: VertexArrayObject? // все три указателя должны хранится во избежание утечки + private val vao: VertexArrayObject? private val indexCount: Int @@ -301,54 +296,48 @@ class Font( glPixelStorei(GL_UNPACK_ALIGNMENT, 4) vao = state.newVAO() - ebo = state.newEBO() - vbo = state.newVBO() + val ebo = state.newEBO() + val vbo = state.newVBO() - vao.bind() - ebo.bind() - vbo.bind() + vao.elementBuffer = ebo + vao.bindAttributes(vbo, VertexAttributes.POSITION_UV) - val builder = VertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, GeometryType.QUADS) + val builder = VertexBuilder(VertexAttributes.POSITION_UV, GeometryType.QUADS) + + builder.vertex(0f, 0f).uv(0f, 0f) + builder.vertex(width, 0f).uv(1f, 0f) + builder.vertex(width, height).uv(1f, 1f) + builder.vertex(0f, height).uv(0f, 1f) - builder.quad(0f, 0f, width, height, QuadTransformers.uv()) builder.upload(vbo, ebo, GL_STATIC_DRAW) - builder.attributes.apply(vao, true) indexCount = builder.indexCount - elementIndexType = builder.indexType - - vao.unbind() - ebo.unbind() - vbo.unbind() } else { isEmpty = true indexCount = 0 elementIndexType = 0 vao = null - vbo = null - ebo = null - texture = null } } - fun render(stack: Matrix4fStack) { + fun render(model: Matrix3f) { if (isEmpty) { - stack.last().translateWithMultiplication(advanceX) + model.translate(advanceX) return } vao!!.bind() - stack.last().translateWithMultiplication(bearingX, -bearingY) + model.translate(bearingX, -bearingY) texture!!.bind() - state.programs.font.transform = stack.last() + state.programs.font.modelMatrix = model glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L) checkForGLError() - stack.last().translateWithMultiplication(advanceX - bearingX, bearingY) + model.translate(advanceX - bearingX, bearingY) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt index 072f4e12..d940cba1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt @@ -2,32 +2,27 @@ package ru.dbotthepony.kstarbound.client.render import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap +import ru.dbotthepony.kvector.arrays.Matrix3fStack import ru.dbotthepony.kvector.arrays.Matrix4fStack +import java.util.function.Function /** * Позволяет вызывать отрисовщики в определённой (послойной) последовательности */ class LayeredRenderer { - private val layers = Long2ObjectAVLTreeMap Unit>>() - private val layersHash = Long2ObjectOpenHashMap Unit>>() + private val layers = Long2ObjectAVLTreeMap Unit>>() - fun add(layer: Long, renderer: (Matrix4fStack) -> Unit) { - var list = layersHash[layer] - - if (list == null) { - list = ArrayList() - layers[layer] = list - layersHash[layer] = list - } - - list.add(renderer) + fun add(layer: Long, renderer: () -> Unit) { + layers.computeIfAbsent(layer, Function { ArrayList() }).add(renderer) } - fun render(stack: Matrix4fStack) { + fun render() { for (list in layers.values) { for (renderer in list) { - renderer.invoke(stack) + renderer.invoke() } } + + layers.clear() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt index a3e2951e..18d213ee 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt @@ -18,25 +18,22 @@ class Mesh() { val vbo = state.newVBO() val ebo = state.newEBO() + init { + vao.elementBuffer = ebo + } + var indexCount = 0 private set var indexType = 0 private set fun load(builder: VertexBuilder, mode: Int = GL45.GL_DYNAMIC_DRAW) { - vao.bind() - vbo.bind() - ebo.bind() - builder.upload(vbo, ebo, mode) - builder.attributes.apply(vao, true) + + vao.bindAttributes(vbo, builder.attributes) indexCount = builder.indexCount indexType = builder.indexType - - vao.unbind() - vbo.unbind() - ebo.unbind() } fun render() { @@ -48,8 +45,8 @@ class Mesh() { } data class ConfiguredMesh(val config: RenderConfig, val mesh: Mesh = Mesh()) { - fun render(transform: Matrix4f = config.client.matrixStack.last()) { - config.setup(transform) + fun render() { + config.setup() mesh.render() config.uninstall() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt index c6d31323..6c182abf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt @@ -7,7 +7,7 @@ abstract class RenderConfig(val program: T) { val client get() = program.client open val initialBuilderCapacity: Int get() = 64 - open fun setup(transform: Matrix4f = client.matrixStack.last()) { + open fun setup() { program.use() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index 3ac2d789..e19c405e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -6,13 +6,13 @@ import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.* -import ru.dbotthepony.kstarbound.client.gl.shader.GLTileProgram +import ru.dbotthepony.kstarbound.client.gl.shader.UberShader import ru.dbotthepony.kstarbound.client.gl.vertex.* import ru.dbotthepony.kstarbound.defs.tile.* import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.TileColor -import ru.dbotthepony.kvector.arrays.Matrix4f +import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2i import kotlin.collections.HashMap @@ -27,6 +27,7 @@ class TileRenderers(val client: StarboundClient) { private val background = HashMap() private val matCache = HashMap() private val modCache = HashMap() + private val identity = Matrix3f.identity() fun getMaterialRenderer(defName: String): TileRenderer { return matCache.computeIfAbsent(defName) { @@ -42,31 +43,32 @@ class TileRenderers(val client: StarboundClient) { } } - private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig(client.programs.tile) { + private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig(client.programs.tile) { override val initialBuilderCapacity: Int get() = 1024 - override fun setup(transform: Matrix4f) { - super.setup(transform) + override fun setup() { + super.setup() client.activeTexture = 0 client.depthTest = false - program.texture = 0 + program.texture0 = 0 texture.bind() texture.textureMagFilter = GL_NEAREST texture.textureMinFilter = GL_NEAREST - program.transform = transform - program.color = color + program.worldMatrix = client.stack.last() + program.modelMatrix = identity + program.colorMultiplier = color } override fun uninstall() {} } - fun foreground(texture: GLTexture2D): RenderConfig { + fun foreground(texture: GLTexture2D): RenderConfig { return foreground.computeIfAbsent(texture) { Config(it, FOREGROUND_COLOR) } } - fun background(texture: GLTexture2D): RenderConfig { + fun background(texture: GLTexture2D): RenderConfig { return background.computeIfAbsent(texture) { Config(it, BACKGROUND_COLOR) } } @@ -146,7 +148,13 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { val (u0, v0) = texture!!.pixelToUV(mins) val (u1, v1) = texture.pixelToUV(maxs) - builder.quadZ(a, b, c, d, Z_LEVEL, QuadTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (isModifier) self.modifierHueShift else self.hueShift) }) + val hue = if (isModifier) self.modifierHueShift else self.hueShift + + // flip uv since in-world coordinates are flipped relative to screen + builder.vertex(a, b).uv(u0, v1).hueShift(hue) + builder.vertex(c, b).uv(u1, v1).hueShift(hue) + builder.vertex(c, d).uv(u1, v0).hueShift(hue) + builder.vertex(a, d).uv(u0, v0).hueShift(hue) } private fun tesselatePiece( @@ -222,7 +230,6 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { } companion object { - const val Z_LEVEL = 10f private val LOGGER = LogManager.getLogger() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt index 6a3dcace..dbc3e84c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt @@ -9,9 +9,6 @@ class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChu private val def = entity.def override fun render(stack: Matrix4fStack) { - client.programs.textured.use() - client.programs.textured.transform = stack.last() - client.activeTexture = 0 - client.programs.textured.texture = 0 + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt index 1d117ede..e89d2d92 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt @@ -142,7 +142,10 @@ class ClientWorld( val state = view.getCell(x, y) if (state?.liquid?.def === type) { - builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level) + builder.vertex(x.toFloat(), y.toFloat()) + builder.vertex(x.toFloat() + 1f, y.toFloat()) + builder.vertex(x.toFloat() + 1f, y.toFloat() + 1f) + builder.vertex(x.toFloat(), y.toFloat() + 1f) } } } @@ -153,37 +156,37 @@ class ClientWorld( for ((baked, zLevel) in background.bakedMeshes) { layers.add(zLevel + RenderLayer.BackgroundTile.index) { - it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - baked.render(it.last()) - it.pop() + client.stack.push().last().translate(renderOrigin.x, renderOrigin.y) + baked.render() + client.stack.pop() } } for ((baked, zLevel) in foreground.bakedMeshes) { layers.add(zLevel + RenderLayer.ForegroundTile.index) { - it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - baked.render(it.last()) - it.pop() + client.stack.push().last().translate(renderOrigin.x, renderOrigin.y) + baked.render() + client.stack.pop() } } - if (liquidMesh.isNotEmpty()) { + /*if (liquidMesh.isNotEmpty()) { layers.add(RenderLayer.Liquid.index) { - it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) + client.stack.push().last().translate(renderOrigin.x, renderOrigin.y) val program = client.programs.liquid program.use() - program.transform = it.last() + program.transform = client.stack.last() for ((mesh, color) in liquidMesh) { program.baselineColor = color mesh.render() } - it.pop() + client.stack.pop() } - } + }*/ } } @@ -266,24 +269,21 @@ class ClientWorld( for (obj in objects) { if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) { //layers.add(RenderLayer.Object.index) { - layers.add(obj.orientation?.renderLayer ?: continue) { m -> + layers.add(obj.orientation?.renderLayer ?: continue) { client.quadWireframe { - it.quad( - obj.pos.x.toFloat(), - obj.pos.y.toFloat(), - obj.pos.x + 1f, - obj.pos.y + 1f, - ) + it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat()) + it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat()) + it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat() + 1f) + it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat() + 1f) } obj.drawables.forEach { val (x, y) = obj.imagePosition - it.render( - client, - x = obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, - y = obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf - ) + client.stack.push().last() + .translate(obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf) + it.render(client) + client.stack.pop() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt index 7006adf3..c04e63c8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt @@ -10,18 +10,23 @@ import com.google.gson.stream.JsonWriter import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.StarboundClient -import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.defs.image.SpriteReference +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.math.LineF +import ru.dbotthepony.kstarbound.util.Either import ru.dbotthepony.kstarbound.util.contains import ru.dbotthepony.kvector.arrays.Matrix3f -import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector3f +import kotlin.math.sin sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) { + @JsonFactory + data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either = Either.left(1f)) + class Line( val line: LineF, val width: Float, @@ -29,7 +34,11 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false ) : Drawable(position, color, fullbright) { - override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { + override fun render(client: StarboundClient, x: Float, y: Float) { + TODO("Not yet implemented") + } + + override fun flop(): Drawable { TODO("Not yet implemented") } } @@ -40,15 +49,18 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false ) : Drawable(position, color, fullbright) { - override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { + override fun render(client: StarboundClient, x: Float, y: Float) { + TODO("Not yet implemented") + } + + override fun flop(): Drawable { TODO("Not yet implemented") } } class Image( val path: SpriteReference, - val transform: Matrix3f, - val centered: Boolean, + val transform: Either, position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false @@ -60,34 +72,73 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig return this } - return Image(newPath, transform, centered, position, color, fullbright) + return Image(newPath, transform, position, color, fullbright) } - override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { + override fun flop(): Drawable { + return Image(path, transform.flatMap({ it.copy().scale(-1f, 1f) }, { it.copy(mirrored = !it.mirrored) }), position, color, fullbright) + } + + override fun render(client: StarboundClient, x: Float, y: Float) { val sprite = path.sprite ?: return val texture = client.loadTexture(path.imagePath.value!!) + val program = client.programs.positionTexture - if (centered) { - client.quadTexture(texture) { - it.quad(x - (sprite.width / PIXELS_IN_STARBOUND_UNITf) * 0.5f, y - (sprite.height / PIXELS_IN_STARBOUND_UNITf) * 0.5f, x + sprite.width / PIXELS_IN_STARBOUND_UNITf * 0.5f, y + sprite.height / PIXELS_IN_STARBOUND_UNITf * 0.5f, QuadTransformers.uv(sprite)) + program.modelMatrix = transform.map({ it }, { + val mat = Matrix3f.identity() + + it.scale.map({ mat.scale(it / PIXELS_IN_STARBOUND_UNITf, it / PIXELS_IN_STARBOUND_UNITf) }, { mat.scale(it / PIXELS_IN_STARBOUND_UNITf) }) + + if (it.centered) { + mat.translate(sprite.width / -2f, sprite.height / -2f) } - } else { - client.quadTexture(texture) { - it.quad(x, y, x + sprite.width / PIXELS_IN_STARBOUND_UNITf, y + sprite.height / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite)) + + if (it.rotation != 0f) { + mat.rotateAroundZ(it.rotation) } - } + + if (it.mirrored) { + mat.translate(sprite.width.toFloat(), 0f) + mat.scale(-1f, 1f) + } + + mat + }) + + program.worldMatrix = client.stack.last() + program.use() + program.texture0 = 0 + client.activeTexture = 0 + texture.bind() + + program.builder.builder.begin(GeometryType.QUADS) + program.builder.builder.vertex(x, y).uv(sprite.u0, sprite.v0) + program.builder.builder.vertex(x + sprite.width, y).uv(sprite.u1, sprite.v0) + program.builder.builder.vertex(x + sprite.width, y + sprite.height).uv(sprite.u1, sprite.v1) + program.builder.builder.vertex(x, y + sprite.height).uv(sprite.u0, sprite.v1) + program.builder.upload() + program.builder.draw() } } class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) { - override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {} + override fun render(client: StarboundClient, x: Float, y: Float) {} + + override fun flop(): Drawable { + return this + } } open fun with(values: (String) -> String?): Drawable { return this } - abstract fun render(client: StarboundClient = StarboundClient.current(), stack: Matrix4fStack = client.matrixStack, x: Float = 0f, y: Float = 0f) + abstract fun render(client: StarboundClient = StarboundClient.current(), x: Float = 0f, y: Float = 0f) + + /** + * mirror along X axis + */ + abstract fun flop(): Drawable companion object { val EMPTY = Empty() @@ -102,6 +153,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig private val colors = gson.getAdapter(RGBAColor::class.java) private val images = gson.getAdapter(SpriteReference::class.java) private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter> + private val transformations = gson.getAdapter(Transformations::class.java) override fun write(out: JsonWriter?, value: Drawable?) { TODO("Not yet implemented") @@ -123,9 +175,9 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig return Poly(vertices.fromJsonTree(value["poly"]), position, color, fullbright) } else if ("image" in value) { val image = images.fromJsonTree(value["image"]) - val mat = Matrix3f.identity() if ("transformation" in value) { + val mat = Matrix3f.identity() val array = value["transformation"].asJsonArray // original starbound use GLM, which reflects OpenGL, which in turn make matrices row-major @@ -144,26 +196,25 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig mat.r20 = row2.x mat.r21 = row2.y mat.r22 = row2.z + + return Image( + image, + Either.left(mat), + position, + color, + fullbright + ) } else { - if ("rotation" in value) { - LOGGER.warn("Rotation is not supported yet (required by ${image.raw})") - } - - if ("mirrored" in value && value["mirrored"].asBoolean) { - mat.scale(-1f, -1f) - } - - if ("scale" in value) { - if (value["scale"].isJsonArray) { - mat.scale(vectors.fromJsonTree(value["scale"])) - } else { - val scale = value["scale"].asFloat - mat.scale(scale, scale) - } - } + return Image( + image, + Either.right(transformations.fromJsonTree(value)), + position, + color, + fullbright + ) } - return Image(image, mat, value["centered"]?.asBoolean ?: false, position, color, fullbright) + } else { return Empty(position, color, fullbright) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt index 803dda88..e3d441ae 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt @@ -130,10 +130,22 @@ data class ObjectOrientation( if ("imageLayers" in obj) { for (value in obj["imageLayers"].asJsonArray) { - drawables.add(this.drawables.fromJsonTree(value)) + var result = this.drawables.fromJsonTree(value) + + if (flipImages) { + result = result.flop() + } + + drawables.add(result) } } else { - drawables.add(this.drawables.fromJsonTree(obj)) + var result = this.drawables.fromJsonTree(obj) + + if (flipImages) { + result = result.flop() + } + + drawables.add(result) } val imagePosition = (obj["imagePosition"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO) / PIXELS_IN_STARBOUND_UNITf diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt index 5af51a08..93563b42 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt @@ -7,59 +7,35 @@ import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter * * JSON адаптер реализуется через [EitherTypeAdapter] */ -sealed class Either { - class Left(val value: L) : Either() { - override val isLeft: Boolean - get() = true - override val isRight: Boolean - get() = false +class Either private constructor(val left: KOptional, val right: KOptional) { + val isLeft: Boolean get() = left.isPresent + val isRight: Boolean get() = right.isPresent - override fun map(left: (L) -> T, right: (R) -> T): T { - return left.invoke(this.value) + inline fun map(left: (L) -> T, right: (R) -> T): T { + this.left.ifPresent { + return left.invoke(it) } - override fun left(): L { - return value - } - - override fun right(): R { - throw NoSuchElementException() - } + return right.invoke(this.right.value) } - class Right(val value: R) : Either() { - override val isLeft: Boolean - get() = false - override val isRight: Boolean - get() = true - - override fun map(left: (L) -> T, right: (R) -> T): T { - return right.invoke(value) + inline fun flatMap(left: (L) -> NL, right: (R) -> NR): Either { + this.left.ifPresent { + return left(left.invoke(it)) } - override fun left(): L { - throw NoSuchElementException() - } - - override fun right(): R { - return value - } + return right(right.invoke(this.right.value)) } - abstract val isLeft: Boolean - abstract val isRight: Boolean - - abstract fun map(left: (L) -> T, right: (R) -> T): T - /** * @throws NoSuchElementException */ - abstract fun left(): L + fun left(): L = left.value /** * @throws NoSuchElementException */ - abstract fun right(): R + fun right(): R = right.value inline fun leftOrElse(orElse: () -> L): L { if (isLeft) @@ -77,11 +53,11 @@ sealed class Either { companion object { fun left(value: L): Either { - return Left(value) + return Either(KOptional.of(value), KOptional.empty()) } fun right(value: R): Either { - return Right(value) + return Either(KOptional.empty(), KOptional.of(value)) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Ext.kt index 8739b3c5..487bcfa7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Ext.kt @@ -8,6 +8,7 @@ import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter +import java.lang.ref.Reference operator fun JsonObject.set(key: String, value: JsonElement?) { add(key, value) } operator fun JsonObject.set(key: String, value: String) { add(key, JsonPrimitive(value)) } @@ -147,3 +148,17 @@ inline fun JsonObject.get(key: String, orElse: () -> T return value } + +inline fun MutableIterable>.forEachValid(block: (T) -> Unit) { + val i = iterator() + + for (v in i) { + val get = v.get() + + if (get == null) { + i.remove() + } else { + block.invoke(get) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/HashTableInterner.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/HashTableInterner.kt index fd610f0f..fd1f6dc6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/HashTableInterner.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/HashTableInterner.kt @@ -9,7 +9,7 @@ import java.lang.ref.WeakReference import java.util.concurrent.locks.LockSupport // hand-rolled interner, which has similar performance to ConcurrentHashMap -// (given there is no strong congestion, in which case it performs somewhere above Caffeine interner), +// (given there is no strong congestion, otherwise it performs somewhere above Caffeine interner), // while yielding significantly better memory utilization than both class HashTableInterner(private val segmentBits: Int) : Interner { companion object { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt index 8bbfaf83..17a10b43 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt @@ -10,6 +10,9 @@ fun KOptional(value: T) = KOptional.of(value) * in more elegant solution than handling nullable Optionals */ class KOptional private constructor(private val _value: T, val isPresent: Boolean) { + /** + * @throws NoSuchElementException + */ val value: T get() { if (isPresent) { return _value diff --git a/src/main/resources/shaders/flat.fsh b/src/main/resources/shaders/flat.fsh deleted file mode 100644 index 846ac241..00000000 --- a/src/main/resources/shaders/flat.fsh +++ /dev/null @@ -1,9 +0,0 @@ - -#version 330 - -uniform vec4 color; -out vec4 color_out; - -void main() { - color_out = color; -} diff --git a/src/main/resources/shaders/flat.vsh b/src/main/resources/shaders/flat.vsh deleted file mode 100644 index 731d50c4..00000000 --- a/src/main/resources/shaders/flat.vsh +++ /dev/null @@ -1,9 +0,0 @@ - -#version 330 - -layout (location = 0) in vec2 pos; -uniform mat4 transform; - -void main() { - gl_Position = transform * vec4(pos, 0.5, 1.0); -} diff --git a/src/main/resources/shaders/fragment/configurable.fsh b/src/main/resources/shaders/fragment/configurable.fsh new file mode 100644 index 00000000..260715b0 --- /dev/null +++ b/src/main/resources/shaders/fragment/configurable.fsh @@ -0,0 +1,59 @@ + +uniform vec4 colorMultiplier; +out vec4 colorResult; + +#ifdef TEXTURE +uniform sampler2D texture0; +in vec2 uvOut; +#endif + +#ifdef COLOR +in vec4 vertexColor; +#endif + +#ifdef HUE_SHIFT +in float hueShiftOut; +#endif + +// https://gist.github.com/983/e170a24ae8eba2cd174f +// http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl +// https://stackoverflow.com/questions/15095909/from-rgb-to-hsv-in-opengl-glsl +// All components are in the range [0…1], including hue. +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +// All components are in the range [0…1], including hue. +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + colorResult = vec4(1, 1, 1, 1); + +#ifdef TEXTURE + colorResult *= texture(texture0, uvOut); +#endif + +#ifdef HUE_SHIFT + vec3 hsv2 = rgb2hsv(colorResult.rgb); + hsv2[0] += hueShiftOut / 360; + colorResult = vec4(hsv2rgb(hsv2), colorResult.a); +#endif + +#ifdef COLOR + colorResult *= vertexColor; +#endif + + colorResult *= colorMultiplier; +} diff --git a/src/main/resources/shaders/fragment/font.fsh b/src/main/resources/shaders/fragment/font.fsh new file mode 100644 index 00000000..7dfffba9 --- /dev/null +++ b/src/main/resources/shaders/fragment/font.fsh @@ -0,0 +1,13 @@ + +#version 330 + +uniform sampler2D texture0; + +uniform vec4 colorMultiplier; +out vec4 colorResult; + +in vec2 uvOut; + +void main() { + colorResult = colorMultiplier * texture(texture0, uvOut).r; +} diff --git a/src/main/resources/shaders/light.vsh b/src/main/resources/shaders/light.vsh index 558f246f..6a374e2d 100644 --- a/src/main/resources/shaders/light.vsh +++ b/src/main/resources/shaders/light.vsh @@ -6,9 +6,10 @@ layout (location = 1) in vec2 uvCoords; out vec2 oUVCoords; -uniform mat4 transform; +uniform mat3 transform; void main() { - gl_Position = transform * vec4(vertexPos, 0.0, 1.0); + vec3 result = transform * vec3(vertexPos, 1.0); + gl_Position = vec4(result.x, result.y, 0.0, result.z); oUVCoords = uvCoords; } diff --git a/src/main/resources/shaders/tile.fsh b/src/main/resources/shaders/tile.fsh deleted file mode 100644 index 1a9f03f4..00000000 --- a/src/main/resources/shaders/tile.fsh +++ /dev/null @@ -1,38 +0,0 @@ - -#version 330 - -uniform sampler2D texture0; -uniform vec4 color; - -in vec2 uv_out; -in float hsv_vertex; -out vec4 color_out; - -// https://gist.github.com/983/e170a24ae8eba2cd174f -vec3 rgb2hsv(vec3 c) -{ - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); - vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} - -vec3 hsv2rgb(vec3 c) -{ - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -void main() { - vec4 texel = texture(texture0, uv_out); - - vec3 hsv2 = rgb2hsv(vec3(texel[0], texel[1], texel[2])); - hsv2[0] += hsv_vertex / 360; - vec4 rgb2 = vec4(hsv2rgb(hsv2), texel[3]); - - color_out = color * rgb2; -} diff --git a/src/main/resources/shaders/tile.vsh b/src/main/resources/shaders/tile.vsh deleted file mode 100644 index 5a0a2815..00000000 --- a/src/main/resources/shaders/tile.vsh +++ /dev/null @@ -1,16 +0,0 @@ - -#version 330 - -layout (location = 0) in vec3 pos; -layout (location = 1) in vec2 uv_in; -layout (location = 2) in float hsv_in; - -out vec2 uv_out; -out float hsv_vertex; -uniform mat4 transform; - -void main() { - uv_out = uv_in; - hsv_vertex = hsv_in; - gl_Position = transform * vec4(pos, 1.0); -} diff --git a/src/main/resources/shaders/vertex/configurable.vsh b/src/main/resources/shaders/vertex/configurable.vsh new file mode 100644 index 00000000..c31008c8 --- /dev/null +++ b/src/main/resources/shaders/vertex/configurable.vsh @@ -0,0 +1,38 @@ + +layout (location = 0) in vec2 pos; + +#ifdef TEXTURE +layout (location = TEXTURE) in vec2 uvIn; +out vec2 uvOut; +#endif + +#ifdef COLOR +layout (location = COLOR) in vec4 colorIn; +out vec4 vertexColor; +#endif + +#ifdef HUE_SHIFT +layout (location = HUE_SHIFT) in float hueShiftIn; +out float hueShiftOut; +#endif + +uniform mat3 viewMatrix; // projection (viewport) + camera +uniform mat3 worldMatrix; // matrix stack +uniform mat3 modelMatrix; // local transformations + +void main() { + vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0); + gl_Position = vec4(result.x, result.y, 0.0, result.z); + +#ifdef HUE_SHIFT + hueShiftOut = hueShiftIn; +#endif + +#ifdef COLOR + vertexColor = colorIn; +#endif + +#ifdef TEXTURE + uvOut = uvIn; +#endif +} diff --git a/src/main/resources/shaders/vertex/font.vsh b/src/main/resources/shaders/vertex/font.vsh new file mode 100644 index 00000000..e2aafaaf --- /dev/null +++ b/src/main/resources/shaders/vertex/font.vsh @@ -0,0 +1,18 @@ + +#version 330 + +layout (location = 0) in vec2 pos; +layout (location = 1) in vec2 uvData; + +uniform mat3 viewMatrix; // projection (viewport) +uniform mat3 worldMatrix; // world position +uniform mat3 modelMatrix; // mesh/local transformations + +out vec2 uvOut; + +void main() { + vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0); + gl_Position = vec4(result.x, result.y, 0.0, result.z); + + uvOut = uvData; +} diff --git a/src/main/resources/shaders/vertex/position.vsh b/src/main/resources/shaders/vertex/position.vsh index 2ab0a57a..b6869ed0 100644 --- a/src/main/resources/shaders/vertex/position.vsh +++ b/src/main/resources/shaders/vertex/position.vsh @@ -3,10 +3,11 @@ layout (location = 0) in vec2 pos; -uniform mat3 viewMatrix; // camera -uniform mat3 worldMatrix; // world position -uniform mat3 modelMatrix; // mesh/local transformations +uniform mat3 viewMatrix; // projection (viewport) + camera +uniform mat3 worldMatrix; // matrix stack +uniform mat3 modelMatrix; // local transformations void main() { - gl_Position = vec4(viewMatrix * worldMatrix * modelMatrix * vec3(pos, 0.0), 1.0); + vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0); + gl_Position = vec4(result.x, result.y, 0.0, result.z); } diff --git a/src/main/resources/shaders/vertex/position_color.vsh b/src/main/resources/shaders/vertex/position_color.vsh index d7f06fde..131d9c9b 100644 --- a/src/main/resources/shaders/vertex/position_color.vsh +++ b/src/main/resources/shaders/vertex/position_color.vsh @@ -4,14 +4,15 @@ layout (location = 0) in vec2 pos; layout (location = 1) in vec4 color; -uniform mat3 viewMatrix; // camera -uniform mat3 worldMatrix; // world position -uniform mat3 modelMatrix; // mesh/local transformations +uniform mat3 viewMatrix; // projection (viewport) + camera +uniform mat3 worldMatrix; // matrix stack +uniform mat3 modelMatrix; // local transformations out vec4 vertexColor; void main() { - gl_Position = vec4(viewMatrix * worldMatrix * modelMatrix * vec3(pos, 0.0), 1.0); + vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0); + gl_Position = vec4(result.x, result.y, 0.0, result.z); vertexColor = color; }