diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt index 38b78149..14f18aa5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt @@ -103,8 +103,6 @@ class ClientWorld( for ((baked, builder, zLevel) in meshes.meshes()) { bakedMeshes.add(ConfiguredMesh(baked, Mesh(state, builder)) to zLevel) } - - meshes.clear() } } 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 fe99e516..40c0aa48 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 @@ -1,5 +1,7 @@ package ru.dbotthepony.kstarbound.client.gl.vertex +import it.unimi.dsi.fastutil.ints.IntList + enum class GeometryType( /** * Число вершин у одного элемента @@ -9,22 +11,22 @@ enum class GeometryType( /** * Индекс вершин одного элемента */ - val indices: IntArray + val indices: IntList ) { - AS_IS(1, intArrayOf(0)), - LINES(2, intArrayOf(0, 1)), - TRIANGLES(3, intArrayOf(0, 1, 2)), + AS_IS(1, IntList.of(0)), + LINES(2, IntList.of(0, 1)), + TRIANGLES(3, IntList.of(0, 1, 2)), /** * A B C B C D */ - QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)), + QUADS(4, IntList.of(0, 1, 2, 1, 2, 3)), /** * A B C C D A */ - QUADS_ALTERNATIVE(4, intArrayOf(0, 1, 2, 2, 3, 0)), + QUADS_ALTERNATIVE(4, IntList.of(0, 1, 2, 2, 3, 0)), - QUADS_AS_LINES(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3)), - QUADS_AS_LINES_WIREFRAME(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)), + 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/VertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexBuilder.kt index abf574db..171044c6 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 @@ -43,7 +43,7 @@ private fun indexSize(type: Int): Int { } /** - * Создаёт буферы вне кучи для загрузки геометрии в них, с набором аттрибутов [attributes], типом [type] и указанным начальным размером. + * Создаёт буферы вне кучи для загрузки геометрии в них, с набором аттрибутов [attributes], типом [mode] и указанным начальным размером. * * По мере необходимости, буферы могут быть увеличены в размерах. * @@ -51,23 +51,28 @@ private fun indexSize(type: Int): Int { */ class VertexBuilder( val attributes: GLAttributeList, - val type: GeometryType, + val defaultMode: GeometryType? = null, initialCapacity: Int = 64 ) { + init { + require(initialCapacity > 0) { "Invalid capacity: $initialCapacity" } + } + + private var mode: GeometryType? = defaultMode + + private var vertexCapacity = if (defaultMode == null) initialCapacity else defaultMode.elements * initialCapacity + private var indexCapacity = if (defaultMode == null) initialCapacity else defaultMode.indices.size * initialCapacity + /** * [GL_UNSIGNED_BYTE], [GL_UNSIGNED_SHORT] или [GL_UNSIGNED_INT] */ - var indexType: Int = chooseIndexType(initialCapacity) + var indexType: Int = chooseIndexType(indexCapacity) private set var indexSize: Int = indexSize(indexType) private set - /** - * В элементах, не вершинах - */ - private var capacity = initialCapacity - private var vertexMemory = ByteBuffer.allocateDirect(capacity * attributes.stride * type.elements).also { it.order(ByteOrder.LITTLE_ENDIAN) } - private var indexMemory = ByteBuffer.allocateDirect(capacity * indexSize * type.indices.size).also { it.order(ByteOrder.LITTLE_ENDIAN) } + 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 indexWriter = writer(indexType) private var inVertex = false @@ -76,16 +81,105 @@ class VertexBuilder( private var elementVertices = 0 - var vertexCount = 0 + var vertexCount = 0 // distinct vertices + private set + var indexCount = 0 // distinct indices + private set + var elementCount = 0 // GeometryType specific (quads, triangles, lines), less than or equal (points) to vertexCount private set - var indexCount = 0 - private set + private fun ensureCapacity() { + val mode = mode ?: return - var elementCount = 0 - private set + if (indexCapacity < indexCount + mode.indices.size) { + indexCapacity = (indexCapacity * 2).coerceAtLeast(indexCount + mode.indices.size) - fun begin() { + // probable double recreation if we also resize vertex array AND it ends up + val indexPos = indexMemory.position() + val indexMemory = ByteBuffer.allocateDirect(indexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) } + this.indexMemory.position(0) + indexMemory.put(this.indexMemory) + indexMemory.position(indexPos) + + this.indexMemory = indexMemory + } + + if (vertexCapacity < vertexCount + mode.elements) { + vertexCapacity = (vertexCapacity * 2).coerceAtLeast(vertexCount + mode.elements) + + val indexType = chooseIndexType(vertexCapacity) + val indexSize = indexSize(indexType) + + val vertexPos = vertexMemory.position() + val vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.stride).also { it.order(ByteOrder.LITTLE_ENDIAN) } + + this.vertexMemory.position(0) + vertexMemory.put(this.vertexMemory) + vertexMemory.position(vertexPos) + + if (indexType != this.indexType) { + val indexMemory = ByteBuffer.allocateDirect(vertexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) } + + when (this.indexType) { + GL_UNSIGNED_BYTE -> { + when (indexType) { + GL_UNSIGNED_BYTE -> throw IllegalArgumentException() + GL_UNSIGNED_SHORT -> { + this.indexMemory.position(0) + + for (i in 0 until indexCount) { + indexMemory.putShort(this.indexMemory.get().toShort()) + } + } + GL_UNSIGNED_INT -> { + this.indexMemory.position(0) + + for (i in 0 until indexCount) { + indexMemory.putInt(this.indexMemory.get().toInt()) + } + } + } + } + + GL_UNSIGNED_SHORT -> { + when (indexType) { + GL_UNSIGNED_BYTE -> throw IllegalArgumentException() + GL_UNSIGNED_SHORT -> throw IllegalArgumentException() + GL_UNSIGNED_INT -> { + this.indexMemory.position(0) + + for (i in 0 until indexCount) { + indexMemory.putInt(this.indexMemory.getShort().toInt()) + } + } + } + } + + else -> throw IllegalArgumentException() + } + + this.indexMemory = indexMemory + this.indexType = indexType + this.indexSize = indexSize + } + + this.vertexMemory = vertexMemory + this.indexWriter = writer(indexType) + } + } + + fun mode(mode: GeometryType): VertexBuilder { + check(!inVertex) { "Can't change buffer geometry type during vertex construction" } + check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" } + this.mode = mode + ensureCapacity() + return this + } + + fun mode() = mode + + fun begin(mode: GeometryType? = defaultMode): VertexBuilder { + this.mode = mode inVertex = false attributeIndex = 0 elementIndexOffset = 0 @@ -94,6 +188,8 @@ class VertexBuilder( elementCount = 0 vertexMemory.position(0) indexMemory.position(0) + ensureCapacity() + return this } private fun checkBounds() { @@ -102,20 +198,24 @@ class VertexBuilder( } } - fun expect(type: GLType) { + 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) { + 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: VertexBufferObject, ebo: VertexBufferObject, drawType: Int = GL46.GL_DYNAMIC_DRAW) { @@ -124,7 +224,7 @@ class VertexBuilder( end() - check(elementVertices == 0) { "Not fully built vertex element ($type requires ${type.elements} vertex points to be present, yet last strip has only $elementVertices elements)" } + check(elementVertices == 0) { "Not fully built vertex element ($mode requires ${mode?.elements} vertex points to be present, yet last strip has only $elementVertices elements)" } val vertexPos = vertexMemory.position() val elementPos = indexMemory.position() @@ -140,100 +240,36 @@ class VertexBuilder( } fun end() { - if (inVertex) { - inVertex = false + if (!inVertex) return - if (attributeIndex != attributes.size) { - throw IllegalStateException("Unfinished vertex, we are at $attributeIndex, while we have ${attributes.size} attributes") + val mode = mode!! + inVertex = false + + if (attributeIndex != attributes.size) { + throw IllegalStateException("Unfinished vertex, we are at $attributeIndex, while we have ${attributes.size} attributes") + } + + vertexCount++ + elementVertices++ + + if (elementVertices == mode.elements) { + elementCount++ + elementVertices = 0 + + for (index in mode.indices.indices) { + indexWriter.write(indexMemory, mode.indices.getInt(index) + elementIndexOffset) } - vertexCount++ - elementVertices++ + elementIndexOffset += mode.elements + indexCount += mode.indices.size - if (elementVertices == type.elements) { - elementCount++ - elementVertices = 0 - - for (index in type.indices) { - indexWriter.write(indexMemory, index + elementIndexOffset) - } - - elementIndexOffset += type.elements - indexCount += type.indices.size - - if (capacity <= elementCount) { - check(this.vertexMemory.position() == this.vertexMemory.capacity()) { "${this.vertexMemory.position()} != ${this.vertexMemory.capacity()}" } - check(this.indexMemory.position() == this.indexMemory.capacity()) { "${this.indexMemory.position()} != ${this.indexMemory.capacity()}" } - - val capacity = capacity * 2 - val indexType = chooseIndexType(capacity) - val indexSize = indexSize(indexType) - - val vertexMemory = ByteBuffer.allocateDirect(capacity * attributes.stride * type.elements) - vertexMemory.order(ByteOrder.LITTLE_ENDIAN) - - this.vertexMemory.position(0) - vertexMemory.put(this.vertexMemory) - - val indexMemory = ByteBuffer.allocateDirect(capacity * indexSize * type.indices.size) - indexMemory.order(ByteOrder.LITTLE_ENDIAN) - - if (indexType != this.indexType) { - when (this.indexType) { - GL_UNSIGNED_BYTE -> { - when (indexType) { - GL_UNSIGNED_BYTE -> throw IllegalArgumentException() - GL_UNSIGNED_SHORT -> { - this.indexMemory.position(0) - - for (i in 0 until this.capacity) { - indexMemory.putShort(this.indexMemory.get().toShort()) - } - } - GL_UNSIGNED_INT -> { - this.indexMemory.position(0) - - for (i in 0 until this.capacity) { - indexMemory.putInt(this.indexMemory.get().toInt()) - } - } - } - } - - GL_UNSIGNED_SHORT -> { - when (indexType) { - GL_UNSIGNED_BYTE -> throw IllegalArgumentException() - GL_UNSIGNED_SHORT -> throw IllegalArgumentException() - GL_UNSIGNED_INT -> { - this.indexMemory.position(0) - - for (i in 0 until this.capacity) { - indexMemory.putInt(this.indexMemory.getShort().toInt()) - } - } - } - } - - else -> throw IllegalArgumentException() - } - } else { - this.indexMemory.position(0) - indexMemory.put(this.indexMemory) - } - - this.capacity = capacity - this.vertexMemory = vertexMemory - this.indexMemory = indexMemory - this.indexType = indexType - this.indexSize = indexSize - this.indexWriter = writer(indexType) - } - } + ensureCapacity() } } fun vertex(): VertexBuilder { end() + checkNotNull(mode) { "No builder mode is set" } inVertex = true attributeIndex = 0 @@ -319,7 +355,7 @@ class VertexBuilder( y1: Float, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM ): VertexBuilder { - check(type.elements == 4) { "Currently building $type" } + check(mode?.elements == 4) { "Currently building $mode" } lambda(vertex().pushVec2f(x0, y0), 0).end() lambda(vertex().pushVec2f(x1, y0), 1).end() @@ -339,7 +375,7 @@ class VertexBuilder( angle: Double, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM ): VertexBuilder { - check(type.elements == 4) { "Currently building $type" } + check(mode?.elements == 4) { "Currently building $mode" } val s = sin(angle).toFloat() val c = cos(angle).toFloat() @@ -370,7 +406,7 @@ class VertexBuilder( z: Float, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM ): VertexBuilder { - check(type.elements == 4) { "Currently building $type" } + check(mode?.elements == 4) { "Currently building $mode" } lambda(vertex().pushVec3f(x0, y0, z), 0).end() lambda(vertex().pushVec3f(x1, y0, z), 1).end() @@ -391,7 +427,7 @@ class VertexBuilder( angle: Double, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM ): VertexBuilder { - check(type.elements == 4) { "Currently building $type" } + check(mode?.elements == 4) { "Currently building $mode" } val s = sin(angle).toFloat() val c = cos(angle).toFloat() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/MultiMeshBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/MultiMeshBuilder.kt index 95add3e0..f4d9783b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/MultiMeshBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/MultiMeshBuilder.kt @@ -16,7 +16,7 @@ class MultiMeshBuilder { fun get(config: RenderConfig<*>, layer: Int): VertexBuilder { return meshes.computeIfAbsent(config, Reference2ObjectFunction { Int2ObjectOpenHashMap() - }).computeIfAbsent(layer, Int2ObjectFunction { Entry(config, VertexBuilder(config.program.attributes, GeometryType.QUADS), layer) }).builder + }).computeIfAbsent(layer, Int2ObjectFunction { Entry(config, VertexBuilder(config.program.attributes), layer) }).builder } fun clear() = meshes.clear() 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 850fe7b2..29b2d0f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt @@ -1,10 +1,11 @@ package ru.dbotthepony.kstarbound.client.render -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram import ru.dbotthepony.kvector.arrays.Matrix4f -abstract class RenderConfig(val state: GLStateTracker, val program: T) { +abstract class RenderConfig(val program: T) { + val state get() = program.state + open fun setup(transform: Matrix4f = state.matrixStack.last()) { 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 213a3c7a..92e29cd2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -43,7 +43,7 @@ class TileRenderers(val client: StarboundClient) { } } - private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig(state, state.programs.tile) { + private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig(state.programs.tile) { override fun setup(transform: Matrix4f) { super.setup(transform) state.activeTexture = 0 @@ -168,7 +168,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { renderers.foreground(state.loadTexture(renderPiece.piece.texture!!)) } - tesselateAt(self, renderPiece.piece, getter, meshBuilder.get(program, def.renderParameters.zLevel), pos, renderPiece.offset, isModifier) + tesselateAt(self, renderPiece.piece, getter, meshBuilder.get(program, def.renderParameters.zLevel).mode(GeometryType.QUADS), pos, renderPiece.offset, isModifier) } else { tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier) } @@ -206,7 +206,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { // то мы просто не можем его отрисовать val template = def.renderTemplate.value ?: return - val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel) + val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel).mode(GeometryType.QUADS) for ((_, matcher) in template.matches) { for (matchPiece in matcher) {