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, 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, 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>() private val callbacks = ArrayList<() -> Unit>() private val builders = Reference2ObjectLinkedOpenHashMap, 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, 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() fun getBuilder(layer: RenderLayer.Point, config: RenderConfig<*>): VertexBuilder { return getLayer(layer).getBuilder(config) } fun bakeIntoMeshes(): Stream, 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() } } }