KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt

129 lines
3.5 KiB
Kotlin

package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap
import org.lwjgl.opengl.GL15.GL_STREAM_DRAW
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import java.util.function.Function
import java.util.stream.Stream
interface IGeometryLayer {
fun add(renderer: () -> Unit)
fun getBuilder(config: RenderConfig<*>): VertexBuilder
fun render()
fun clear()
fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>>
}
object OneShotGeometryLayer : IGeometryLayer {
override fun add(renderer: () -> Unit) {
TODO("Not yet implemented")
}
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
TODO("Not yet implemented")
}
override fun render() {
TODO("Not yet implemented")
}
override fun clear() {
TODO("Not yet implemented")
}
override fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
TODO("Not yet implemented")
}
}
class LayeredRenderer {
class Layer(val layer: RenderLayer.Point) : IGeometryLayer {
private data class Tracker(val builder: StreamVertexBuilder, var emptyFrames: Int = 0)
private val meshes = ArrayList<ConfiguredMesh<*>>()
private val callbacks = ArrayList<() -> Unit>()
private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, Tracker>()
override fun add(renderer: () -> Unit) {
callbacks.add(renderer)
}
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
return builders.computeIfAbsent(config, Function {
Tracker(StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity))
}).builder.builder
}
override fun render() {
val iterator = builders.entries.iterator()
for ((k, v) in iterator) {
if (v.builder.builder.isNotEmpty()) {
v.emptyFrames = 0
k.setup()
v.builder.upload(GL_STREAM_DRAW)
v.builder.draw()
k.uninstall()
} else {
v.emptyFrames++
if (v.emptyFrames >= 600) {
// ~10 seconds of inactivity, free resources
iterator.remove()
}
}
}
meshes.forEach {
it.render()
}
callbacks.forEach {
it.invoke()
}
}
override fun clear() {
builders.values.forEach { it.builder.builder.begin() }
meshes.clear()
callbacks.clear()
}
override fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
if (meshes.isNotEmpty() || callbacks.isNotEmpty())
throw IllegalStateException("This layer (index $layer) contains preconfigured meshes and/or callbacks")
return builders.entries.stream()
.filter { it.value.builder.builder.isNotEmpty() }
.map { ConfiguredMesh(it.key, Mesh(it.value.builder.builder)) to layer }
}
}
private val layers = Object2ObjectAVLTreeMap<RenderLayer.Point, Layer>()
fun getBuilder(layer: RenderLayer.Point, config: RenderConfig<*>): VertexBuilder {
return getLayer(layer).getBuilder(config)
}
fun bakeIntoMeshes(): Stream<Pair<ConfiguredMesh<*>, RenderLayer.Point>> {
return layers.values.stream().flatMap { it.bakeIntoMeshes() }
}
fun add(layer: RenderLayer.Point, renderer: () -> Unit) {
getLayer(layer).add(renderer)
}
fun getLayer(layer: RenderLayer.Point): Layer {
return layers.computeIfAbsent(layer, Function { Layer(it) })
}
fun render() {
layers.values.forEach { it.render() }
}
fun clear() {
layers.values.forEach { it.clear() }
}
}