Redo vertex builder

This commit is contained in:
DBotThePony 2022-09-11 01:10:40 +07:00
parent 48cf205506
commit 194a7e479c
Signed by: DBot
GPG Key ID: DCC23B5715498507
18 changed files with 688 additions and 773 deletions

View File

@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.client
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.gl.VertexTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
import ru.dbotthepony.kstarbound.defs.ParallaxPrototype
@ -10,7 +10,6 @@ import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
import ru.dbotthepony.kstarbound.util.DoubleEdgeProgression
import ru.dbotthepony.kstarbound.world.*
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.matrix.Matrix4fStack
import ru.dbotthepony.kvector.util2d.AABB
class ClientWorld(
@ -49,8 +48,7 @@ class ClientWorld(
client.gl.shaderVertexTexture.use()
val stateful = client.gl.flat2DTexturedQuads.statefulSmall
val builder = stateful.builder
val builder = client.gl.flat2DTexturedQuads.small
client.gl.activeTexture = 0
client.gl.shaderVertexTexture["_texture"] = 0
@ -80,7 +78,7 @@ class ClientWorld(
x0 += diffx.toFloat() / PIXELS_IN_STARBOUND_UNITf
x1 += diffx.toFloat() / PIXELS_IN_STARBOUND_UNITf
builder.quadZ(x0, diffy, x1, diffy + texture.height.toFloat() / PIXELS_IN_STARBOUND_UNITf, 1f, VertexTransformers.uv(0f, 1f, 1f, 0f))
builder.quadZ(x0, diffy, x1, diffy + texture.height.toFloat() / PIXELS_IN_STARBOUND_UNITf, 1f, QuadTransformers.uv(0f, 1f, 1f, 0f))
/*if (x1 < size.mins.x) {
break
@ -91,8 +89,8 @@ class ClientWorld(
}
}
stateful.upload()
stateful.draw()
builder.upload()
builder.draw()
client.gl.matrixStack.pop()
}

View File

@ -1,45 +1,19 @@
package ru.dbotthepony.kstarbound.client.gl
import com.google.common.collect.ImmutableList
import org.lwjgl.opengl.GL46.*
enum class GLType(val identity: Int, val typeIndentity: Int, val byteSize: Int, val logicalSize: 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),
VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2),
VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3),
VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4),
VEC2I(GL_INT_VEC2, GL_INT, 8, 2),
VEC3I(GL_INT_VEC3, GL_INT, 12, 3),
VEC4I(GL_INT_VEC4, GL_INT, 16, 4),
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),
}
interface IGLAttributeList {
/**
* Применяет список атрибутов к заданному [GLVertexArrayObject] (попутно включая или отключая их через [enable])
*/
fun apply(target: GLVertexArrayObject, enable: Boolean = false)
}
data class AttributeListPosition(val name: String, val index: Int, val glType: GLType, val stride: Int, val offset: Long)
/**
* Хранит список аттрибутов для применения к Vertex Array Object
*
* Аттрибуты плотно упакованы и идут один за другим
*
* Создаётся через [GLFlatAttributeListBuilder]
* Создаётся через [GLAttributeList.Builder]
*/
class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeList {
val attributes: List<AttributeListPosition>
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<Attribute>
val size get() = attributes.size
/**
@ -49,10 +23,8 @@ class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeLis
operator fun get(index: Int) = attributes[index]
fun vertexBuilder(vertexType: VertexType) = DynamicVertexBuilder(this, vertexType)
init {
val buildList = ArrayList<AttributeListPosition>()
val buildList = ArrayList<Attribute>()
var offset = 0L
var stride = 0
@ -63,20 +35,19 @@ class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeLis
this.stride = stride
// val counter = mutableMapOf<Int, Int>()
for (i in builder.attributes.indices) {
val value = builder.attributes[i].second
buildList.add(AttributeListPosition(builder.attributes[i].first, i, value, stride, offset))
buildList.add(Attribute(builder.attributes[i].first, i, value, stride, offset))
offset += value.byteSize
// counter[value.typeIndentity] = (counter[value.typeIndentity] ?: 0) + 1
}
attributes = ImmutableList.copyOf(buildList)
}
override fun apply(target: GLVertexArrayObject, enable: Boolean) {
/**
* Применяет список атрибутов к заданному [GLVertexArrayObject] (попутно включая или отключая их через [enable])
*/
fun apply(target: GLVertexArrayObject, 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)
@ -87,66 +58,36 @@ class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeLis
}
}
class Builder {
val attributes = ArrayList<Pair<String, GLType>>()
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 = GLFlatAttributeListBuilder().push(GLType.VEC2F).build()
val VEC3F = GLFlatAttributeListBuilder().push(GLType.VEC3F).build()
val VEC2F = Builder().push(GLType.VEC2F).build()
val VEC3F = Builder().push(GLType.VEC3F).build()
val VERTEX_TEXTURE = GLFlatAttributeListBuilder().push(GLType.VEC3F).push(GLType.VEC2F).build()
val VERTEX_TEXTURE = Builder().push(GLType.VEC3F).push(GLType.VEC2F).build()
val VERTEX_HSV_TEXTURE = GLFlatAttributeListBuilder()
val VERTEX_HSV_TEXTURE = Builder()
.push(GLType.VEC3F, GLType.VEC2F, GLType.FLOAT).build()
val VERTEX_2D_TEXTURE = GLFlatAttributeListBuilder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLFlatAttributeListBuilder : IGLAttributeList {
val attributes = ArrayList<Pair<String, GLType>>()
private fun findName(name: String): Boolean {
for (value in attributes) {
if (value.first == name) {
return true
}
}
return false
}
fun push(type: GLType): GLFlatAttributeListBuilder {
return push("$type#${attributes.size}", type)
}
fun push(vararg types: GLType): GLFlatAttributeListBuilder {
for (type in types) push(type)
return this
}
fun push(name: String, type: GLType): GLFlatAttributeListBuilder {
check(!findName(name)) { "Already has named attribute $name!" }
attributes.add(name to type)
return this
}
fun build() = GLFlatAttributeList(this)
@Deprecated("Используй build()", replaceWith = ReplaceWith("build()"))
override fun apply(target: GLVertexArrayObject, enable: Boolean) {
var offset = 0L
var stride = 0
for (i in attributes) {
stride += i.second.byteSize
}
for (i in attributes.indices) {
val value = attributes[i].second
target.attribute(i, value.logicalSize, value.typeIndentity, false, stride, offset)
offset += value.byteSize
if (enable) {
target.enableAttribute(i)
}
}
val VERTEX_2D_TEXTURE = Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}

View File

@ -5,6 +5,8 @@ import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.freetype.FreeType
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType
import ru.dbotthepony.kstarbound.client.render.Box2DRenderer
import ru.dbotthepony.kstarbound.client.render.Font
import ru.dbotthepony.kstarbound.client.render.TileRenderers
@ -86,7 +88,6 @@ interface GLCleanable : Cleaner.Cleanable {
interface GLStreamBuilderList {
val small: StreamVertexBuilder
val statefulSmall: StatefulStreamVertexBuilder
}
class GLStateTracker {
@ -403,61 +404,37 @@ class GLStateTracker {
val flat2DLines = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.LINES, 1024)
}
override val statefulSmall by lazy {
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.LINES, 1024)
}
}
val flat2DTriangles = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.TRIANGLES, 1024)
}
override val statefulSmall by lazy {
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.TRIANGLES, 1024)
}
}
val flat2DQuads = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS, 1024)
}
override val statefulSmall by lazy {
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS, 1024)
}
}
val flat2DTexturedQuads = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS, 1024)
}
override val statefulSmall by lazy {
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VERTEX_TEXTURE, VertexType.QUADS, 1024)
}
}
val flat2DQuadLines = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS_AS_LINES, 1024)
}
override val statefulSmall by lazy {
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES, 1024)
}
}
val flat2DQuadWireframe = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 1024)
}
override val statefulSmall by lazy {
return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small)
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 1024)
}
}
@ -467,20 +444,17 @@ class GLStateTracker {
val font = Font(this)
inline fun quadWireframe(color: Color = Color.WHITE, lambda: (StreamVertexBuilder) -> Unit) {
val stateful = flat2DQuadWireframe.statefulSmall
val builder = stateful.builder
val builder = flat2DQuadWireframe.small
builder.begin()
lambda.invoke(builder)
stateful.upload()
builder.upload()
flatProgram.use()
flatProgram.color.set(color)
flatProgram.transform.set(matrixStack.last)
stateful.draw(GL_LINES)
builder.draw(GL_LINES)
}
inline fun quadWireframe(value: AABB, color: Color = Color.WHITE, chain: (StreamVertexBuilder) -> Unit = {}) {

View File

@ -0,0 +1,27 @@
package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.*
enum class GLType(
val identity: Int,
val typeIndentity: Int,
val byteSize: Int,
val logicalSize: 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),
VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2),
VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3),
VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4),
VEC2I(GL_INT_VEC2, GL_INT, 8, 2),
VEC3I(GL_INT_VEC3, GL_INT, 12, 3),
VEC4I(GL_INT_VEC4, GL_INT, 16, 4),
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),
}

View File

@ -1,601 +0,0 @@
package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kvector.util2d.AABB
import java.io.Closeable
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.collections.ArrayList
import kotlin.math.cos
import kotlin.math.sin
enum class VertexType(val elements: Int, val indicies: IntArray) {
LINES(2, intArrayOf(0, 1)),
TRIANGLES(3, intArrayOf(0, 1, 2)),
QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)),
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)),
}
interface IVertexBuilder<This : IVertexBuilder<This, VertexType>, VertexType : IVertex<VertexType, This>> {
val type: ru.dbotthepony.kstarbound.client.gl.VertexType
val indexCount: Int
fun begin(): This
fun vertex(): VertexType
fun checkValid()
fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int = GL_DYNAMIC_DRAW)
fun quad(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
lambda: VertexTransformer = emptyTransform
): This {
check(type.elements == 4) { "Currently building $type" }
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()
return this as This
}
fun quadRotated(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
x: Float,
y: Float,
angle: Double,
lambda: VertexTransformer = emptyTransform
): This {
check(type.elements == 4) { "Currently building $type" }
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()
return this as This
}
fun quad(aabb: AABB, lambda: VertexTransformer = emptyTransform): This {
return quad(
aabb.mins.x.toFloat(),
aabb.mins.y.toFloat(),
aabb.maxs.x.toFloat(),
aabb.maxs.y.toFloat(),
lambda
)
}
fun quadZ(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
lambda: VertexTransformer = emptyTransform
): This {
check(type.elements == 4) { "Currently building $type" }
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 as This
}
fun quadRotatedZ(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
x: Float,
y: Float,
angle: Double,
lambda: VertexTransformer = emptyTransform
): This {
check(type.elements == 4) { "Currently building $type" }
val s = sin(angle).toFloat()
val c = cos(angle).toFloat()
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()
return this as This
}
}
interface IVertex<This : IVertex<This, VertexBuilderType>, VertexBuilderType> {
fun checkValid()
fun expect(name: String): This
fun expect(type: GLType): This
fun pushVec3f(x: Float, y: Float, z: Float): This
fun pushVec2f(x: Float, y: Float): This
fun push(value: Float): This
fun end(): VertexBuilderType
}
typealias VertexTransformer = (IVertex<*, *>, Int) -> IVertex<*, *>
private val emptyTransform: VertexTransformer = { it, _ -> it }
private val EMPTY_BUFFER = ByteBuffer.allocateDirect(0)
fun VertexTransformer.before(other: VertexTransformer): VertexTransformer {
return { a, b ->
other.invoke(a, b)
this.invoke(a, b)
}
}
fun VertexTransformer.after(other: VertexTransformer): VertexTransformer {
return { a, b ->
this.invoke(a, b)
other.invoke(a, b)
}
}
object VertexTransformers {
fun uv(u0: Float,
v0: Float,
u1: Float,
v1: Float,
lambda: VertexTransformer = emptyTransform
): VertexTransformer {
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(): VertexTransformer {
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(lambda: VertexTransformer): VertexTransformer {
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 lambda(it, index)
}
}
}
class DynamicVertexBuilder(val attributes: GLFlatAttributeList, override val type: VertexType) : IVertexBuilder<DynamicVertexBuilder, DynamicVertexBuilder.Vertex> {
private val verticies = ArrayList<Vertex>()
override val indexCount get() = (verticies.size / type.elements) * type.indicies.size
override fun begin(): DynamicVertexBuilder {
verticies.clear()
return this
}
override fun vertex(): Vertex {
return Vertex()
}
override fun checkValid() {
for (vertex in verticies) {
vertex.checkValid()
}
}
/**
* Загружает (копирует) данные в указанные буферы, с их текущей позиции
*/
fun upload(
vertexBuffer: ByteBuffer,
elementBuffer: ByteBuffer,
) {
check(verticies.size % type.elements == 0) { "Not fully built (expected ${type.elements} verticies to be present for each element, last element has only ${verticies.size % type.elements})" }
require(vertexBuffer.order() == ByteOrder.nativeOrder()) { "Byte order of $vertexBuffer does not match native order" }
require(elementBuffer.order() == ByteOrder.nativeOrder()) { "Byte order of $elementBuffer does not match native order" }
checkValid()
for (vertex in verticies) {
vertex.upload(vertexBuffer)
}
var offsetVertex = 0
for (i in 0 until verticies.size / type.elements) {
for (i2 in type.indicies.indices) {
elementBuffer.putInt(type.indicies[i2] + offsetVertex)
}
offsetVertex += type.elements
}
}
/**
* Загружает буфер в указанные VBO и EBO
*
* операция создаёт мусор вне кучи и довольно медленная
*/
override fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int) {
require(vbo.isArray) { "$vbo is not an array" }
require(ebo.isElementArray) { "$vbo is not an element array" }
check(verticies.size % type.elements == 0) { "Not fully built (expected ${type.elements} verticies to be present for each element, last element has only ${verticies.size % type.elements})" }
checkValid()
if (verticies.size == 0) {
vbo.bufferData(EMPTY_BUFFER, drawType)
ebo.bufferData(EMPTY_BUFFER, drawType)
return
}
val vertexBuffer = ByteBuffer.allocateDirect(verticies.size * attributes.stride)
vertexBuffer.order(ByteOrder.nativeOrder())
val elementBuffer = ByteBuffer.allocateDirect((verticies.size / type.elements) * type.indicies.size * 4)
elementBuffer.order(ByteOrder.nativeOrder())
upload(vertexBuffer, elementBuffer)
check(vertexBuffer.position() == vertexBuffer.capacity()) { "Vertex Buffer is not fully filled (position: ${vertexBuffer.position()}; capacity: ${vertexBuffer.capacity()})" }
check(elementBuffer.position() == elementBuffer.capacity()) { "Element Buffer is not fully filled (position: ${elementBuffer.position()}; capacity: ${elementBuffer.capacity()})" }
vertexBuffer.position(0)
elementBuffer.position(0)
vbo.bufferData(vertexBuffer, drawType)
ebo.bufferData(elementBuffer, drawType)
}
inner class Vertex : IVertex<Vertex, DynamicVertexBuilder> {
init {
verticies.add(this)
}
private val store = arrayOfNulls<Any>(attributes.size)
private var index = 0
fun upload(bytes: ByteBuffer) {
for (element in store) {
when (element) {
is FloatArray -> for (i in element) bytes.putFloat(i)
is IntArray -> for (i in element) bytes.putInt(i)
is ByteArray -> for (i in element) bytes.put(i)
is DoubleArray -> for (i in element) bytes.putDouble(i)
is Float -> bytes.putFloat(element)
else -> throw IllegalStateException("Unknown element $element")
}
}
}
override fun toString(): String {
return "Vertex(${store.map {
return@map when (it) {
is FloatArray -> it.joinToString(", ")
is IntArray -> it.joinToString(", ")
is ByteArray -> it.joinToString(", ")
is DoubleArray -> it.joinToString(", ")
is Float -> it
else -> "null"
} }.joinToString("; ")})"
}
override fun expect(name: String): Vertex {
if (index >= attributes.size) {
throw IllegalStateException("Reached end of attribute list early, expected $name")
}
if (attributes[index].name != name) {
throw IllegalStateException("Expected $name, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
}
return this
}
override fun expect(type: GLType): Vertex {
if (index >= attributes.size) {
throw IllegalStateException("Reached end of attribute list early, expected type $type")
}
if (attributes[index].glType != type) {
throw IllegalStateException("Expected $type, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
}
return this
}
override fun pushVec3f(x: Float, y: Float, z: Float): Vertex {
expect(GLType.VEC3F)
store[index++] = floatArrayOf(x, y, z)
return this
}
override fun pushVec2f(x: Float, y: Float): Vertex {
expect(GLType.VEC2F)
store[index++] = floatArrayOf(x, y)
return this
}
override fun push(value: Float): Vertex {
expect(GLType.FLOAT)
store[index++] = value
return this
}
override fun checkValid() {
for (elem in store.indices) {
if (store[elem] == null) {
throw IllegalStateException("Vertex element at position $elem is null")
}
}
}
override fun end(): DynamicVertexBuilder {
checkValid()
return this@DynamicVertexBuilder
}
}
}
/**
* "Поточная" версия [DynamicVertexBuilder], ориентированная на скорость работы и имеющая фиксированный размер буфера
*
* Главные отличия:
* * Данный объект не желательно создавать каждый раз когда надо отрисовать какое либо количество геометрии, а использовать уже существующий, который
* удовлетворяет требованиям (формат вершин, и их потенциально максимальное количество)
* * Максимальное количество vertex'ов фиксированно и равняется [maxElements] * [VertexType.elements]
* * Имеет два встроенных DirectByteBuffer и НЕ позволяет загружать данные в другие ByteBuffer, только во внутренние ByteBuffer и только в VBO; EBO
*/
class StreamVertexBuilder(
val attributes: GLFlatAttributeList,
override val type: VertexType,
val maxElements: Int,
) : IVertexBuilder<StreamVertexBuilder, StreamVertexBuilder.Vertex> {
val maxVertexNum = maxElements * type.elements
var nextVertex = 0
private set
override val indexCount get() = (nextVertex / type.elements) * type.indicies.size
val maxIndexCount = maxElements * type.indicies.size
val elementIndexType = when (maxIndexCount) {
// api performance issue 102: glDrawElements uses element index type 'GL_UNSIGNED_BYTE' that is not optimal for the current hardware configuration; consider using 'GL_UNSIGNED_SHORT' instead
// in 0 .. 255 -> GL_UNSIGNED_BYTE
in 0 .. 65535 -> GL_UNSIGNED_SHORT
else -> GL_UNSIGNED_INT
}
private var head: Vertex? = null
/**
* Буфер для VBO достаточного размера
*/
private val vertexBuffer = ByteBuffer.allocateDirect(maxVertexNum * attributes.stride)
/**
* Буфер для EBO достаточного размера
*/
private val elementBuffer = ByteBuffer.allocateDirect(maxElements * type.indicies.size * 4)
init {
vertexBuffer.order(ByteOrder.nativeOrder())
elementBuffer.order(ByteOrder.nativeOrder())
}
private fun writeElementIndex(value: Int) {
when (elementIndexType) {
GL_UNSIGNED_BYTE -> elementBuffer.put(value.toByte())
GL_UNSIGNED_SHORT -> elementBuffer.putShort(value.toShort())
else -> elementBuffer.putInt(value)
}
}
private var offsetElementIndex = 0
/**
* Устанавливает метку этого билдера в ноль.
*
* Не обнуляет память буферов!
*/
override fun begin(): StreamVertexBuilder {
nextVertex = 0
offsetElementIndex = 0
head = null
vertexBuffer.position(0)
elementBuffer.position(0)
return this
}
override fun vertex(): Vertex {
return Vertex()
}
override fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int) {
require(vbo.isArray) { "$vbo is not an array" }
require(ebo.isElementArray) { "$vbo is not an element array" }
if (nextVertex == 0) {
vbo.bufferData(EMPTY_BUFFER, drawType)
ebo.bufferData(EMPTY_BUFFER, drawType)
return
}
checkValid()
val a = vertexBuffer.position().toLong()
val b = elementBuffer.position().toLong()
vertexBuffer.position(0)
elementBuffer.position(0)
vbo.bufferData(vertexBuffer, drawType, length = a)
ebo.bufferData(elementBuffer, drawType, length = b)
}
override fun checkValid() {
var vertex = head
while (vertex != null) {
vertex.checkValid()
vertex = vertex.previous
}
}
inner class Vertex : IVertex<Vertex, StreamVertexBuilder> {
private val vertexIndex = nextVertex++
val previous = head
private var bufferPosition = vertexIndex * attributes.stride
init {
if (vertexIndex >= maxVertexNum) {
throw IndexOutOfBoundsException("Tried to push new vertex $vertexIndex, when already above limit of $maxVertexNum!")
}
head = this
for (i2 in type.indicies.indices) {
writeElementIndex(type.indicies[i2] + offsetElementIndex)
}
offsetElementIndex += type.elements
}
private var index = 0
override fun checkValid() {
check(index == attributes.size) { "Vertex $vertexIndex is not fully filled (only $index attributes provided, ${attributes.size} required)" }
}
override fun expect(name: String): Vertex {
if (index >= attributes.size) {
throw IllegalStateException("Reached end of attribute list early, expected $name")
}
if (attributes[index].name != name) {
throw IllegalStateException("Expected $name, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
}
return this
}
override fun expect(type: GLType): Vertex {
if (index >= attributes.size) {
throw IllegalStateException("Reached end of attribute list early, expected type $type")
}
if (attributes[index].glType != type) {
throw IllegalStateException("Expected $type, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
}
return this
}
override fun pushVec3f(x: Float, y: Float, z: Float): Vertex {
expect(GLType.VEC3F)
vertexBuffer.position(bufferPosition)
vertexBuffer.putFloat(x)
vertexBuffer.putFloat(y)
vertexBuffer.putFloat(z)
index++
bufferPosition += 12
return this
}
override fun pushVec2f(x: Float, y: Float): Vertex {
expect(GLType.VEC2F)
vertexBuffer.position(bufferPosition)
vertexBuffer.putFloat(x)
vertexBuffer.putFloat(y)
index++
bufferPosition += 8
return this
}
override fun push(value: Float): Vertex {
expect(GLType.FLOAT)
vertexBuffer.position(bufferPosition)
vertexBuffer.putFloat(value)
index++
bufferPosition += 4
return this
}
override fun end(): StreamVertexBuilder {
check(index == attributes.size) { "Vertex $vertexIndex is not fully filled (only $index attributes provided, ${attributes.size} required)" }
return this@StreamVertexBuilder
}
}
}
class StatefulStreamVertexBuilder(
val state: GLStateTracker,
val builder: StreamVertexBuilder
) : Closeable, IVertexBuilder<StreamVertexBuilder, StreamVertexBuilder.Vertex> by builder {
private val vao = state.newVAO()
private val vbo = state.newVBO()
private val ebo = state.newEBO()
init {
vao.bind()
vbo.bind()
ebo.bind()
builder.attributes.apply(vao, true)
vao.unbind()
vbo.unbind()
ebo.unbind()
}
fun upload(drawType: Int = GL_DYNAMIC_DRAW) {
builder.upload(vbo, ebo, drawType)
}
fun bind() = vao.bind()
fun unbind() = vao.unbind()
fun draw(primitives: Int = GL_TRIANGLES) {
bind()
glDrawElements(primitives, builder.indexCount, builder.elementIndexType, 0L)
checkForGLError()
}
override fun close() {
vao.close()
vbo.close()
ebo.close()
}
}

View File

@ -0,0 +1,262 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import org.lwjgl.opengl.GL46
import org.lwjgl.opengl.GL46.GL_UNSIGNED_INT
import org.lwjgl.opengl.GL46.GL_UNSIGNED_SHORT
import org.lwjgl.opengl.GL46.GL_UNSIGNED_BYTE
import ru.dbotthepony.kstarbound.client.gl.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.GLType
import ru.dbotthepony.kstarbound.client.gl.GLVertexBufferObject
import ru.dbotthepony.kstarbound.util.writeLEFloat
import ru.dbotthepony.kstarbound.util.writeLEInt
import ru.dbotthepony.kstarbound.util.writeLEShort
import ru.dbotthepony.kvector.util2d.AABB
import java.io.OutputStream
import kotlin.math.cos
import kotlin.math.sin
private fun put(type: Int, memory: OutputStream, value: Int) {
when (type) {
GL_UNSIGNED_SHORT -> memory.writeLEShort(value)
GL_UNSIGNED_BYTE -> memory.write(value)
else -> memory.writeLEInt(value)
}
}
/**
* Класс для построения геометрии для загрузки в память видеокарты.
*/
@Suppress("unchecked_cast")
abstract class AbstractVertexBuilder<T : AbstractVertexBuilder<T>>(
val attributes: GLAttributeList,
val type: VertexType,
) {
protected abstract val vertexMemory: OutputStream
protected abstract val elementMemory: OutputStream
/**
* [GL_UNSIGNED_BYTE], [GL_UNSIGNED_SHORT] или [GL_UNSIGNED_INT]
*
* Обязана быть статичным числом.
*
* Если нет, и его надо изменить, то это можно только делать внутри [ensureIndexCapacity].
*
* Подкласс обязан самостоятельно изменить тип существующих элементов внутри [elementMemory]
*/
abstract val elementIndexType: Int
protected abstract fun ensureIndexCapacity()
protected abstract fun resetMemory()
fun begin() {
inVertex = false
attributeIndex = 0
elementIndexOffset = 0
vertexCount = 0
indexCount = 0
resetMemory()
}
private var inVertex = false
private var attributeIndex = 0
protected var elementIndexOffset = 0
private set
var vertexCount = 0
private set
var indexCount = 0
private set
protected fun checkBounds() {
if (attributeIndex >= attributes.size) {
throw IndexOutOfBoundsException("Tried to add new attribute when already added all attributes")
}
}
fun expect(type: GLType) {
checkBounds()
if (attributes[attributeIndex].glType != type) {
throw IllegalStateException("Expected attribute type $type, got ${attributes[attributeIndex].name}[${attributes[attributeIndex].glType}] (at position $attributeIndex)")
}
}
fun expect(name: String) {
checkBounds()
if (attributes[attributeIndex].name != name) {
throw IllegalStateException("Expected attribute name $name, got ${attributes[attributeIndex].name}[${attributes[attributeIndex].glType}] (at position $attributeIndex)")
}
}
protected abstract fun doUpload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int)
fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int = GL46.GL_DYNAMIC_DRAW) {
require(vbo.isArray) { "$vbo is not an array" }
require(ebo.isElementArray) { "$vbo is not an element array" }
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)" }
doUpload(vbo, ebo, drawType)
}
private var elementVertices = 0
fun end() {
if (inVertex) {
inVertex = false
if (attributeIndex != attributes.size) {
throw IllegalStateException("Unfinished vertex, we are at $attributeIndex, while we have ${attributes.size} attributes")
}
vertexCount++
elementVertices++
if (elementVertices == type.elements) {
ensureIndexCapacity()
elementVertices = 0
val elementMemory = elementMemory
val elementIndexType = elementIndexType
for (index in type.indicies) {
put(elementIndexType, elementMemory, index + elementIndexOffset)
}
elementIndexOffset += type.elements
indexCount += type.indicies.size
}
}
}
fun vertex(): T {
end()
inVertex = true
attributeIndex = 0
return this as T
}
fun pushVec3f(x: Float, y: Float, z: Float): T {
expect(GLType.VEC3F)
val memory = vertexMemory
memory.writeLEFloat(x)
memory.writeLEFloat(y)
memory.writeLEFloat(z)
attributeIndex++
return this as T
}
fun pushVec2f(x: Float, y: Float): T {
expect(GLType.VEC2F)
val memory = vertexMemory
memory.writeLEFloat(x)
memory.writeLEFloat(y)
attributeIndex++
return this as T
}
fun push(value: Float): T {
expect(GLType.FLOAT)
vertexMemory.writeLEFloat(value)
attributeIndex++
return this as T
}
// Помощники
fun quad(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
check(type.elements == 4) { "Currently building $type" }
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()
return this as T
}
fun quadRotated(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
x: Float,
y: Float,
angle: Double,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
check(type.elements == 4) { "Currently building $type" }
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()
return this as T
}
fun quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): T {
return quad(
aabb.mins.x.toFloat(),
aabb.mins.y.toFloat(),
aabb.maxs.x.toFloat(),
aabb.maxs.y.toFloat(),
lambda
)
}
fun quadZ(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
check(type.elements == 4) { "Currently building $type" }
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 as T
}
fun quadRotatedZ(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
x: Float,
y: Float,
angle: Double,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): T {
check(type.elements == 4) { "Currently building $type" }
val s = sin(angle).toFloat()
val c = cos(angle).toFloat()
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()
return this as T
}
}

View File

@ -0,0 +1,50 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.GLVertexBufferObject
import ru.dbotthepony.kstarbound.util.ByteBufferOutputStream
open class DirectVertexBuilder(
attributes: GLAttributeList,
type: VertexType,
val maxElements: Int,
) : AbstractVertexBuilder<HeapVertexBuilder>(attributes, type) {
val maxIndexCount = maxElements * type.indicies.size
val maxVertexCount = maxElements * type.elements
override val vertexMemory = ByteBufferOutputStream.directLE(maxVertexCount)
override val elementMemory = ByteBufferOutputStream.directLE(maxIndexCount)
override val elementIndexType: Int = when (maxIndexCount) {
// api performance issue 102: glDrawElements uses element index type 'GL_UNSIGNED_BYTE' that is not optimal for the current hardware configuration; consider using 'GL_UNSIGNED_SHORT' instead
// in 0 .. 255 -> GL_UNSIGNED_BYTE
in 0 .. 65535 -> GL46.GL_UNSIGNED_SHORT
else -> GL46.GL_UNSIGNED_INT
}
override fun ensureIndexCapacity() {
if (vertexCount >= maxVertexCount) {
throw IndexOutOfBoundsException("Vertex count overflow (can hold max of $maxElements elements, that's $maxVertexCount vertexes)")
}
}
override fun resetMemory() {
vertexMemory.position = 0
elementMemory.position = 0
}
override fun doUpload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int) {
val vertexPos = vertexMemory.position
val elementPos = elementMemory.position
vertexMemory.position = 0
elementMemory.position = 0
vbo.bufferData(vertexMemory.buffer, drawType, length = vertexPos.toLong())
ebo.bufferData(elementMemory.buffer, drawType, length = elementPos.toLong())
vertexMemory.position = vertexPos
elementMemory.position = elementPos
}
}

View File

@ -0,0 +1,63 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import org.lwjgl.opengl.GL11.GL_UNSIGNED_INT
import org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT
import ru.dbotthepony.kstarbound.client.gl.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.GLVertexBufferObject
import ru.dbotthepony.kstarbound.util.readLEShort
import ru.dbotthepony.kstarbound.util.writeLEInt
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* Vertex Builder который хранит все данные на куче, при [upload] создаётся [ByteBuffer] вне кучи,
* в него записываются данные из кучи, данные загружаются на видеокарту.
*
* Полезен в случаях, когда размер данных для загрузки заранее не известен.
*/
class HeapVertexBuilder(
attributes: GLAttributeList,
type: VertexType,
) : AbstractVertexBuilder<HeapVertexBuilder>(attributes, type) {
override val vertexMemory = FastByteArrayOutputStream()
override val elementMemory = FastByteArrayOutputStream()
override var elementIndexType: Int = GL_UNSIGNED_SHORT
private set
override fun ensureIndexCapacity() {
if (elementIndexType == GL_UNSIGNED_SHORT && elementMemory.length / 2 + type.indicies.size >= 30000) {
val backing = elementMemory.array
val copy = FastByteArrayInputStream(ByteArray(elementMemory.length) { backing[it] })
elementIndexType = GL_UNSIGNED_INT
val elementMemory = elementMemory
elementMemory.reset()
for (i in 0 until (copy.length / 2)) {
elementMemory.writeLEInt(copy.readLEShort())
}
}
}
override fun resetMemory() {
vertexMemory.reset()
elementMemory.reset()
}
override fun doUpload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int) {
val vboMemory = ByteBuffer.allocateDirect(vertexMemory.length)
vboMemory.put(vertexMemory.array, 0, vertexMemory.length)
val eboMemory = ByteBuffer.allocateDirect(elementMemory.length)
eboMemory.put(elementMemory.array, 0, elementMemory.length)
vboMemory.position(0)
eboMemory.position(0)
vbo.bufferData(vboMemory, drawType)
ebo.bufferData(eboMemory, drawType)
}
}

View File

@ -0,0 +1,65 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
typealias QuadVertexTransformer = (AbstractVertexBuilder<*>, Int) -> AbstractVertexBuilder<*>
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(lambda: QuadVertexTransformer): 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 lambda(it, index)
}
}
}

View File

@ -0,0 +1,52 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import java.io.Closeable
/**
* Класс-помощник для быстрого построения, загрузки и отрисовки геометрии
*/
class StreamVertexBuilder(
val state: GLStateTracker,
attributes: GLAttributeList,
type: VertexType,
maxElements: Int,
) : DirectVertexBuilder(attributes, type, maxElements), Closeable {
private val vao = state.newVAO()
private val vbo = state.newVBO()
private val ebo = state.newEBO()
init {
vao.bind()
vbo.bind()
ebo.bind()
attributes.apply(vao, true)
vao.unbind()
vbo.unbind()
ebo.unbind()
}
fun upload(drawType: Int = GL46.GL_DYNAMIC_DRAW) {
upload(vbo, ebo, drawType)
}
fun bind() = vao.bind()
fun unbind() = vao.unbind()
fun draw(primitives: Int = GL46.GL_TRIANGLES) {
bind()
GL46.glDrawElements(primitives, indexCount, elementIndexType, 0L)
checkForGLError()
}
override fun close() {
vao.close()
vbo.close()
ebo.close()
}
}

View File

@ -0,0 +1,9 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
enum class VertexType(val elements: Int, val indicies: IntArray) {
LINES(2, intArrayOf(0, 1)),
TRIANGLES(3, intArrayOf(0, 1, 2)),
QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)),
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)),
}

View File

@ -20,32 +20,30 @@ class Box2DRenderer(val state: GLStateTracker) : IDebugDraw {
override fun drawPolygon(vertices: List<Vector2d>, color: Color) {
require(vertices.size > 1) { "Vertex list had only ${vertices.size} namings in it" }
val stateful = state.flat2DLines.statefulSmall
val builder = stateful.builder
val builder = state.flat2DLines.small
builder.begin()
for (i in vertices.indices) {
val current = vertices[i]
val next = vertices[(i + 1) % vertices.size]
builder.Vertex().pushVec2f(current.x.toFloat(), current.y.toFloat())
builder.Vertex().pushVec2f(next.x.toFloat(), next.y.toFloat())
builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat())
builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat())
}
stateful.upload()
builder.upload()
state.flatProgram.use()
state.flatProgram.color.set(color)
state.flatProgram.transform.set(state.matrixStack.last)
stateful.draw(GL_LINES)
builder.draw(GL_LINES)
}
private fun drawSolid(vertices: List<Vector2d>, color: Color) {
require(vertices.size >= 3) { "Vertex list had only ${vertices.size} namings in it" }
val stateful = state.flat2DTriangles.statefulSmall
val builder = stateful.builder
val builder = state.flat2DTriangles.small
builder.begin()
@ -54,18 +52,18 @@ class Box2DRenderer(val state: GLStateTracker) : IDebugDraw {
for (i in 1 until vertices.size) {
val current = vertices[i]
val next = vertices[(i + 1) % vertices.size]
builder.Vertex().pushVec2f(zero.x.toFloat(), zero.y.toFloat())
builder.Vertex().pushVec2f(current.x.toFloat(), current.y.toFloat())
builder.Vertex().pushVec2f(next.x.toFloat(), next.y.toFloat())
builder.vertex().pushVec2f(zero.x.toFloat(), zero.y.toFloat())
builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat())
builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat())
}
stateful.upload()
builder.upload()
state.flatProgram.use()
state.flatProgram.color.set(color)
state.flatProgram.transform.set(state.matrixStack.last)
stateful.draw(GL_TRIANGLES)
builder.draw(GL_TRIANGLES)
}
override fun drawSolidPolygon(vertices: List<Vector2d>, color: Color) {

View File

@ -3,8 +3,8 @@ package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.client.gl.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.GLVertexArrayObject
import ru.dbotthepony.kstarbound.client.gl.DynamicVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.vertex.AbstractVertexBuilder
import ru.dbotthepony.kvector.api.IFloatMatrix
import ru.dbotthepony.kvector.matrix.Matrix4fStack
@ -43,13 +43,15 @@ class ConfiguredStaticMesh(
val programState: ConfiguredShaderProgram,
val indexCount: Int,
val vao: GLVertexArrayObject,
val elementIndexType: Int,
) : AutoCloseable {
private var onClose = {}
constructor(programState: ConfiguredShaderProgram, builder: DynamicVertexBuilder) : this(
constructor(programState: ConfiguredShaderProgram, builder: AbstractVertexBuilder<*>) : this(
programState,
builder.indexCount,
programState.program.state.newVAO(),
builder.elementIndexType,
) {
val vbo = programState.program.state.newVBO()
val ebo = programState.program.state.newEBO()
@ -80,7 +82,7 @@ class ConfiguredStaticMesh(
}
vao.bind()
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0L)
glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L)
checkForGLError()
}

View File

@ -1,11 +1,10 @@
package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.ClientChunk
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.VertexTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
import ru.dbotthepony.kvector.matrix.Matrix4fStack
@ -70,8 +69,7 @@ open class ProjectileRenderer(state: GLStateTracker, entity: Projectile, chunk:
animator.advance()
val stateful = state.flat2DTexturedQuads.statefulSmall
val builder = stateful.builder
val builder = state.flat2DTexturedQuads.small
builder.begin()
@ -81,9 +79,9 @@ open class ProjectileRenderer(state: GLStateTracker, entity: Projectile, chunk:
val width = (animator.frameObj.width / PIXELS_IN_STARBOUND_UNITf) / 2f
val height = (animator.frameObj.height / PIXELS_IN_STARBOUND_UNITf) / 2f
builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, VertexTransformers.uv(u0, v0, u1, v1))
builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, QuadTransformers.uv(u0, v0, u1, v1))
stateful.upload()
stateful.draw()
builder.upload()
builder.draw()
}
}

View File

@ -6,6 +6,10 @@ import org.lwjgl.opengl.GL46.*
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.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.HeapVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType
import ru.dbotthepony.kvector.matrix.Matrix4fStack
import ru.dbotthepony.kvector.vector.Color
@ -259,6 +263,8 @@ class Font(
private val indexCount: Int
private val elementIndexType: Int
init {
face.loadChar(char, LoadFlag.RENDER)
val glyph = face.nativeMemory.glyph ?: throw IllegalStateException("Unable to load glyph data for $char (code ${char.code})")
@ -301,19 +307,22 @@ class Font(
ebo.bind()
vbo.bind()
val builder = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_2D_TEXTURE, VertexType.QUADS)
val builder = HeapVertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, VertexType.QUADS)
builder.quad(0f, 0f, width, height, VertexTransformers.uv())
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.elementIndexType
vao.unbind()
ebo.unbind()
vbo.unbind()
} else {
isEmpty = true
indexCount = 0
elementIndexType = 0
vao = null
vbo = null
@ -335,7 +344,7 @@ class Font(
texture!!.bind()
state.fontProgram.transform.set(stack.last)
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0L)
glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L)
checkForGLError()
stack.translateWithMultiplication(advanceX - bearingX, bearingY)

View File

@ -7,6 +7,8 @@ import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.gl.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.TileState
import ru.dbotthepony.kstarbound.world.ITileChunk
@ -16,7 +18,7 @@ import kotlin.collections.HashMap
data class TileLayer(
val bakedProgramState: ConfiguredShaderProgram,
val vertexBuilder: DynamicVertexBuilder,
val vertexBuilder: AbstractVertexBuilder<*>,
val zPos: Int
)
@ -28,7 +30,7 @@ class TileLayerList {
*
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
*/
fun computeIfAbsent(program: ConfiguredShaderProgram, zLevel: Int, compute: () -> DynamicVertexBuilder): DynamicVertexBuilder {
fun computeIfAbsent(program: ConfiguredShaderProgram, zLevel: Int, compute: () -> AbstractVertexBuilder<*>): AbstractVertexBuilder<*> {
return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
}).vertexBuilder
@ -166,7 +168,7 @@ private enum class TileRenderTesselateResult {
HALT
}
private fun vertexTextureBuilder() = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_HSV_TEXTURE, VertexType.QUADS)
private fun vertexTextureBuilder() = HeapVertexBuilder(GLAttributeList.VERTEX_HSV_TEXTURE, VertexType.QUADS)
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(tile: TileState?): Boolean {
@ -194,7 +196,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) {
val bakedBackgroundProgramState = state.tileRenderers.background(texture)
// private var notifiedDepth = false
private fun tesselateAt(self: TileState, piece: RenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
private fun tesselateAt(self: TileState, piece: RenderPiece, getter: ITileChunk, builder: AbstractVertexBuilder<*>, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
val fx = pos.x.toFloat()
val fy = pos.y.toFloat()
@ -232,7 +234,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) {
val (u0, v0) = texture.pixelToUV(mins)
val (u1, v1) = texture.pixelToUV(maxs)
builder.quadZ(a, b, c, d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (!isModifier || self.modifier?.grass == true) self.hueShift else 0f) })
builder.quadZ(a, b, c, d, Z_LEVEL, QuadTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (!isModifier || self.modifier?.grass == true) self.hueShift else 0f) })
}
private fun tesselatePiece(
@ -241,7 +243,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) {
getter: ITileChunk,
layers: TileLayerList,
pos: Vector2i,
thisBuilder: DynamicVertexBuilder,
thisBuilder: AbstractVertexBuilder<*>,
background: Boolean,
isModifier: Boolean,
): TileRenderTesselateResult {

View File

@ -0,0 +1,27 @@
package ru.dbotthepony.kstarbound.util
import java.io.OutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
class ByteBufferOutputStream(val buffer: ByteBuffer) : OutputStream() {
override fun write(b: Int) {
buffer.put(b.toByte())
}
override fun write(b: ByteArray) {
buffer.put(b)
}
override fun write(b: ByteArray, off: Int, len: Int) {
buffer.put(b, off, len)
}
var position: Int
get() = buffer.position()
set(value) { buffer.position(value) }
companion object {
fun directLE(capacity: Int) = ByteBufferOutputStream(ByteBuffer.allocateDirect(capacity).also { it.order(ByteOrder.LITTLE_ENDIAN) })
}
}

View File

@ -0,0 +1,39 @@
package ru.dbotthepony.kstarbound.util
import java.io.InputStream
import java.io.OutputStream
fun OutputStream.writeLEInt(value: Int) {
write(value)
write(value ushr 8)
write(value ushr 16)
write(value ushr 24)
}
fun OutputStream.writeLEShort(value: Int) {
write(value)
write(value ushr 8)
}
fun OutputStream.writeLELong(value: Long) {
writeLEInt(value.toInt())
writeLEInt((value ushr 32).toInt())
}
fun OutputStream.writeLEFloat(value: Float) = writeLEInt(value.toBits())
fun OutputStream.writeLEDouble(value: Double) = writeLELong(value.toBits())
fun InputStream.readLEShort(): Int {
return read() or (read() shl 8)
}
fun InputStream.readLEInt(): Int {
return read() or (read() shl 8) or (read() shl 16) or (read() shl 24)
}
fun InputStream.readLELong(): Long {
return readLEInt().toLong() or (readLEInt().toLong() shl 32)
}
fun InputStream.readLEFloat() = Float.fromBits(readLEInt())
fun InputStream.readLEDouble() = Double.fromBits(readLELong())