Make vertex builder be able to switch geometry type on the fly
This commit is contained in:
parent
96cc44c592
commit
386d71b92f
@ -103,8 +103,6 @@ class ClientWorld(
|
||||
for ((baked, builder, zLevel) in meshes.meshes()) {
|
||||
bakedMeshes.add(ConfiguredMesh(baked, Mesh(state, builder)) to zLevel)
|
||||
}
|
||||
|
||||
meshes.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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<out T : GLShaderProgram>(val state: GLStateTracker, val program: T) {
|
||||
abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
|
||||
val state get() = program.state
|
||||
|
||||
open fun setup(transform: Matrix4f = state.matrixStack.last()) {
|
||||
program.use()
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class TileRenderers(val client: StarboundClient) {
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(state, state.programs.tile) {
|
||||
private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(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) {
|
||||
|
Loading…
Reference in New Issue
Block a user