diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index 7eaf8f13..01a68ba2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -26,75 +26,9 @@ import java.util.LinkedList const val Z_LEVEL_BACKGROUND = 60000 const val Z_LEVEL_LIQUID = 10000 -class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk(world, pos), Closeable { +class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk(world, pos){ val state: GLStateTracker get() = world.client.gl - private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable { - private val layers = TileLayerList() - val bakedMeshes = LinkedList>() - var isDirty = false - - fun bake() { - if (!isDirty) return - isDirty = false - - if (state.isSameThread()) { - for (mesh in bakedMeshes) { - mesh.first.close() - } - } - - bakedMeshes.clear() - - layers.clear() - - for (x in 0 until CHUNK_SIZE) { - for (y in 0 until CHUNK_SIZE) { - if (!world.inBounds(x, y)) continue - val tile = view.getTile(x, y) ?: continue - val material = tile.material - - if (material != null) { - world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground) - } - - val modifier = tile.modifier - - if (modifier != null) { - world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true) - } - } - } - } - - fun upload() { - if (layers.isNotEmpty) { - for (mesh in bakedMeshes) { - mesh.first.close() - } - - bakedMeshes.clear() - - for ((baked, builder, zLevel) in layers.buildSortedLayerList()) { - bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel) - } - - layers.clear() - } - } - - override fun close() { - for (mesh in bakedMeshes) { - mesh.first.close() - } - } - } - - val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd) - - private val foregroundRenderer = TileLayerRenderer(worldForegroundView, isBackground = false) - private val backgroundRenderer = TileLayerRenderer(worldBackgroundView, isBackground = true) - override fun foregroundChanges(cell: Cell) { super.foregroundChanges(cell) @@ -111,99 +45,11 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk() - private var liquidTypesVer = 0 - - private fun getLiquidTypes(): Collection { - if (liquidTypesVer != liquidChangeset) { - liquidTypes.clear() - liquidTypesVer = liquidChangeset - - for (x in 0 until CHUNK_SIZE) { - for (y in 0 until CHUNK_SIZE) { - getCell(x, y).liquid.def?.let { liquidTypes.add(it) } - } - } - } - - return liquidTypes - } - - inner class Renderer(val renderOrigin: Vector2f) { - fun addLayers(layers: LayeredRenderer) { - for ((baked, zLevel) in backgroundRenderer.bakedMeshes) { - layers.add(zLevel + Z_LEVEL_BACKGROUND) { - it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - baked.renderStacked(it) - it.pop() - } - } - - for ((baked, zLevel) in foregroundRenderer.bakedMeshes) { - layers.add(zLevel) { - it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - baked.renderStacked(it) - it.pop() - } - } - - for (renderer in entityRenderers.values) { - layers.add(renderer.layer) { - val relative = renderer.renderPos - posVector2d - it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat()) - renderer.render(it) - it.pop() - } - } - - val types = getLiquidTypes() - - if (types.isNotEmpty()) { - layers.add(Z_LEVEL_LIQUID) { - it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - - val program = state.programs.liquid - - program.use() - program.transform = it.last() - - val builder = program.builder - - for (type in types) { - builder.builder.begin() - - for (x in 0 until CHUNK_SIZE) { - for (y in 0 until CHUNK_SIZE) { - val state = getCell(x, y) - - if (state.liquid.def === type) { - builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level) - } - } - } - - program.baselineColor = type.color - - builder.upload() - builder.draw() - } - - it.pop() - } - } + world.forEachRenderRegion(cell) { + it.liquidIsDirty = true } } @@ -226,13 +72,4 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk>() + var liquidIsDirty = true + val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight) val background = Layer(TileView.Background(view), true) @@ -113,6 +120,37 @@ class ClientWorld( background.bake() foreground.bake() + if (liquidIsDirty) { + liquidIsDirty = false + liquidMesh.clear() + + val liquidTypes = ReferenceArraySet() + + for (x in 0 until renderRegionWidth) { + for (y in 0 until renderRegionHeight) { + view.getCell(x, y)?.liquid?.def?.let { liquidTypes.add(it) } + } + } + + for (type in liquidTypes) { + val builder = client.gl.programs.liquid.builder.builder + + builder.begin() + + for (x in 0 until renderRegionWidth) { + for (y in 0 until renderRegionHeight) { + val state = view.getCell(x, y) + + if (state?.liquid?.def === type) { + builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level) + } + } + } + + liquidMesh.add(Mesh(client.gl, builder) to type.color) + } + } + for ((baked, zLevel) in background.bakedMeshes) { layers.add(zLevel + Z_LEVEL_BACKGROUND) { it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) @@ -129,50 +167,23 @@ class ClientWorld( } } - /*for (renderer in entityRenderers.values) { - layers.add(renderer.layer) { - val relative = renderer.renderPos - posVector2d - it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat()) - renderer.render(it) - it.pop() - } - }*/ - - /*val types = getLiquidTypes() - - if (types.isNotEmpty()) { + if (liquidMesh.isNotEmpty()) { layers.add(Z_LEVEL_LIQUID) { it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - val program = state.programs.liquid + val program = client.gl.programs.liquid program.use() program.transform = it.last() - val builder = program.builder - - for (type in types) { - builder.builder.begin() - - for (x in 0 until CHUNK_SIZE) { - for (y in 0 until CHUNK_SIZE) { - val state = getCell(x, y) - - if (state.liquid.def === type) { - builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level) - } - } - } - - program.baselineColor = type.color - - builder.upload() - builder.draw() + for ((mesh, color) in liquidMesh) { + program.baselineColor = color + mesh.render() } it.pop() } - }*/ + } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt new file mode 100644 index 00000000..dfbb1b49 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.kstarbound.client.render + +import org.lwjgl.opengl.GL46 +import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder + +class Mesh(state: GLStateTracker) { + constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) { + load(builder, GL46.GL_STATIC_DRAW) + } + + val vao = state.newVAO() + val vbo = state.newVBO() + val ebo = state.newEBO() + + var indexCount = 0 + private set + var indexType = 0 + private set + + fun load(builder: VertexBuilder, mode: Int = GL46.GL_DYNAMIC_DRAW) { + vao.bind() + vbo.bind() + ebo.bind() + + builder.upload(vbo, ebo, mode) + builder.attributes.apply(vao, true) + + indexCount = builder.indexCount + indexType = builder.indexType + + vao.unbind() + vbo.unbind() + ebo.unbind() + } + + fun render() { + vao.bind() + GL46.glDrawElements(GL46.GL_TRIANGLES, indexCount, indexType, 0L) + checkForGLError() + } +} 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 daa2dd1d..80331f55 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.kstarbound.client.render -import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectFunction +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf @@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2i +import java.util.stream.Stream import kotlin.collections.HashMap data class TileLayer( @@ -25,7 +26,7 @@ data class TileLayer( ) class TileLayerList { - private val layers = HashMap, Int2ObjectAVLTreeMap>() + private val layers = HashMap, Int2ObjectOpenHashMap>() /** * Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel]. @@ -33,24 +34,13 @@ class TileLayerList { * Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute]. */ fun computeIfAbsent(program: ConfiguredShaderProgram, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder { - return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction { + return layers.computeIfAbsent(program) { Int2ObjectOpenHashMap() }.computeIfAbsent(zLevel, Int2ObjectFunction { return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel) }).vertices } - fun buildSortedLayerList(): List { - val list = ArrayList() - - for (getList in layers.values) { - list.addAll(getList.values) - } - - list.sortBy { - // унарный минус для инвентирования порядка (сначала большие, потом маленькие) - return@sortBy -it.zPos - } - - return list + fun layers(): Stream { + return layers.values.stream().flatMap { it.values.stream() } } fun clear() = layers.clear()