Updated render classes structure to be less alien

This commit is contained in:
DBotThePony 2023-09-06 00:04:48 +07:00
parent 370c93226b
commit ef838d52c2
Signed by: DBot
GPG Key ID: DCC23B5715498507
19 changed files with 137 additions and 391 deletions

View File

@ -1,21 +1,10 @@
package ru.dbotthepony.kstarbound.client
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.TileLayerList
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.world.*
import ru.dbotthepony.kstarbound.world.CHUNK_SIZEd
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i
import java.io.Closeable
import java.util.LinkedList
/**
* Псевдо zPos у фоновых тайлов
@ -70,6 +59,6 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
}
override fun onEntityRemoved(entity: Entity) {
entityRenderers.remove(entity)!!.close()
entityRenderers.remove(entity)
}
}

View File

@ -4,10 +4,10 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.LongArraySet
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.Mesh
import ru.dbotthepony.kstarbound.client.render.TileLayerList
import ru.dbotthepony.kstarbound.client.render.MultiMeshBuilder
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
@ -51,23 +51,16 @@ class ClientWorld(
inner class RenderRegion(val x: Int, val y: Int) {
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
private val state get() = client.gl
private val layers = TileLayerList()
val bakedMeshes = ArrayList<Pair<ConfiguredStaticMesh, Int>>()
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Int>>()
var isDirty = true
fun bake() {
if (!isDirty) return
isDirty = false
if (state.isSameThread()) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
}
bakedMeshes.clear()
layers.clear()
val meshes = MultiMeshBuilder()
for (x in 0 until renderRegionWidth) {
for (y in 0 until renderRegionHeight) {
@ -77,30 +70,22 @@ class ClientWorld(
val material = tile.material
if (material != null) {
client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground)
client.tileRenderers.getMaterialRenderer(material.materialName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground)
}
val modifier = tile.modifier
if (modifier != null) {
client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true)
client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground, isModifier = true)
}
}
}
if (layers.isNotEmpty) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.layers()) {
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
}
layers.clear()
for ((baked, builder, zLevel) in meshes.meshes()) {
bakedMeshes.add(ConfiguredMesh(baked, Mesh(state, builder)) to zLevel)
}
meshes.clear()
}
}
@ -150,7 +135,7 @@ class ClientWorld(
for ((baked, zLevel) in background.bakedMeshes) {
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.renderStacked(it)
baked.render(it.last())
it.pop()
}
}
@ -158,7 +143,7 @@ class ClientWorld(
for ((baked, zLevel) in foreground.bakedMeshes) {
layers.add(zLevel) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.renderStacked(it)
baked.render(it.last())
it.pop()
}
}

View File

@ -16,9 +16,7 @@ data class BlendFunc(
Func.ZERO
)
constructor(
source: Func, destination: Func
) : this(
constructor(source: Func, destination: Func) : this(
source,
destination,
source,

View File

@ -13,7 +13,7 @@ import org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus
import org.lwjgl.opengl.GL45.glNamedFramebufferTexture
import org.lwjgl.opengl.GL46
class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable {
class GLFrameBuffer(val state: GLStateTracker) {
init {
state.ensureSameThread()
}
@ -22,6 +22,7 @@ class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable {
init {
checkForGLError("Creating framebuffer")
state.registerCleanable(this, GL46::glDeleteFramebuffers, pointer)
}
val isComplete: Boolean get() {
@ -50,7 +51,6 @@ class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable {
}
fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) {
texture?.close()
texture = null
attachTexture(width, height, format)
}
@ -76,17 +76,4 @@ class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable {
state.readFramebuffer = null
}
}
private val cleanable = state.registerCleanable(this, GL46::glDeleteFramebuffers, pointer)
var isValid = true
private set
override fun close() {
if (!isValid)
return
cleanable.clean()
texture?.close()
isValid = false
}
}

View File

@ -29,7 +29,7 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
}
@Suppress("SameParameterValue")
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
init {
state.ensureSameThread()
}
@ -38,10 +38,9 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
init {
checkForGLError()
state.registerCleanable(this, ::glDeleteTextures, pointer)
}
private val cleanable = state.registerCleanable(this, ::glDeleteTextures, pointer)
var width = 0
private set
@ -269,23 +268,6 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
return this
}
var isValid = true
private set
override fun close() {
state.ensureSameThread()
if (!isValid)
return
if (state.texture2D == this) {
state.texture2D = null
}
cleanable.clean()
isValid = false
}
companion object {
private val LOGGER = LogManager.getLogger()
}

View File

@ -1,29 +1,24 @@
package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.*
import java.io.Closeable
class VertexArrayObject(val state: GLStateTracker) : Closeable {
class VertexArrayObject(val state: GLStateTracker) {
val pointer = glGenVertexArrays()
init {
checkForGLError()
state.registerCleanable(this, ::glDeleteVertexArrays, pointer)
}
private val cleanable = state.registerCleanable(this, ::glDeleteVertexArrays, pointer)
fun bind(): VertexArrayObject {
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
return state.bind(this)
}
fun unbind(): VertexArrayObject {
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
return state.unbind(this)
}
fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): VertexArrayObject {
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
state.ensureSameThread()
glVertexAttribPointer(position, size, type, normalize, stride, offset)
checkForGLError()
@ -31,27 +26,10 @@ class VertexArrayObject(val state: GLStateTracker) : Closeable {
}
fun enableAttribute(position: Int): VertexArrayObject {
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
state.ensureSameThread()
glEnableVertexArrayAttrib(pointer, position)
//glEnableVertexAttribArray(position)
checkForGLError()
return this
}
var isValid = true
private set
override fun close() {
state.ensureSameThread()
if (!isValid) return
if (state.VAO == this) {
state.VAO = null
}
cleanable.clean()
isValid = false
}
}

View File

@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL46.*
import org.lwjgl.system.MemoryUtil
import java.io.Closeable
import java.nio.ByteBuffer
enum class VBOType(val glType: Int) {
@ -10,32 +9,28 @@ enum class VBOType(val glType: Int) {
ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER),
}
class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) : Closeable {
class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) {
val pointer = glGenBuffers()
init {
checkForGLError("Creating Vertex Buffer Object")
state.registerCleanable(this, ::glDeleteBuffers, pointer)
}
private val cleanable = state.registerCleanable(this, ::glDeleteBuffers, pointer)
val isArray get() = type == VBOType.ARRAY
val isElementArray get() = type == VBOType.ELEMENT_ARRAY
fun bind(): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.bind(this)
return this
}
fun unbind(): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.unbind(this)
return this
}
fun bufferData(data: ByteBuffer, usage: Int): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
@ -43,7 +38,6 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
}
fun bufferData(data: ByteBuffer, usage: Int, length: Long): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.ensureSameThread()
if (length > data.remaining().toLong()) {
@ -57,7 +51,6 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
}
fun bufferData(data: IntArray, usage: Int): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
@ -65,7 +58,6 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
}
fun bufferData(data: FloatArray, usage: Int): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
@ -73,7 +65,6 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
}
fun bufferData(data: DoubleArray, usage: Int): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
@ -81,27 +72,9 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.
}
fun bufferData(data: LongArray, usage: Int): VertexBufferObject {
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
state.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
var isValid = true
private set
override fun close() {
state.ensureSameThread()
if (!isValid) return
if (state.VBO == this) {
state.VBO = null
}
cleanable.clean()
isValid = false
}
}

View File

@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
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.GLAttributeList
import ru.dbotthepony.kvector.api.IStruct2f
import ru.dbotthepony.kvector.api.IStruct3f
import ru.dbotthepony.kvector.api.IStruct4f
@ -22,7 +23,11 @@ import kotlin.NoSuchElementException
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class GLShaderProgram(val state: GLStateTracker, shaders: Iterable<GLStateTracker.Shader>) {
open class GLShaderProgram(
val state: GLStateTracker,
shaders: Iterable<GLStateTracker.Shader>,
val attributes: GLAttributeList
) {
init {
state.ensureSameThread()
}

View File

@ -22,7 +22,7 @@ private fun GLStateTracker.gshaders(name: String): List<GLStateTracker.Shader> {
)
}
class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("liquid")) {
class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("liquid"), FORMAT) {
var baselineColor by F4Uniform("baselineColor")
var transform by F4x4Uniform("transform")
@ -35,7 +35,7 @@ class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shad
}
}
class GLLightProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("light")) {
class GLLightProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("light"), FORMAT) {
var baselineColor by F4Uniform("baselineColor")
var transform by F4x4Uniform("transform")
@ -48,7 +48,7 @@ class GLLightProgram(state: GLStateTracker) : GLShaderProgram(state, state.shade
}
}
class GLColorQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad")) {
class GLColorQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad"), FORMAT) {
var color by F4Uniform("color")
private val builder by lazy {
@ -94,7 +94,7 @@ class GLColorQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.s
}
}
class GLTextureQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex")) {
class GLTextureQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex"), FORMAT) {
var texture by IUniform("texture0")
private val builder by lazy {
@ -118,7 +118,7 @@ class GLTextureQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state
}
}
class GLTextureBlurredQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex_blur")) {
class GLTextureBlurredQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex_blur"), FORMAT) {
var texture by IUniform("texture0")
private val builder by lazy {
@ -142,7 +142,7 @@ class GLTextureBlurredQuadProgram(state: GLStateTracker) : GLShaderProgram(state
}
}
class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat_color")) {
class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat_color"), FORMAT) {
var transform by F4x4Uniform("transform")
val builder by lazy {
@ -154,7 +154,22 @@ class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.s
}
}
class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("tile")) {
class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("tile"), FORMAT) {
var transform by F4x4Uniform("transform")
var color by F4Uniform("color")
var texture by IUniform("texture0")
init {
transform = Matrix4f.identity()
color = RGBAColor.WHITE
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F, GLType.FLOAT).build()
}
}
class GLFontProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) {
var transform by F4x4Uniform("transform")
var color by F4Uniform("color")
var texture by IUniform("texture0")
@ -165,18 +180,7 @@ class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader
}
}
class GLFontProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("font")) {
var transform by F4x4Uniform("transform")
var color by F4Uniform("color")
var texture by IUniform("texture0")
init {
transform = Matrix4f.identity()
color = RGBAColor.WHITE
}
}
class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat")) {
class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat"), GLAttributeList.VEC2F) {
var transform by F4x4Uniform("transform")
var color by F4Uniform("color")
@ -185,7 +189,7 @@ class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader
}
}
class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture.glsl"))) {
class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform")
var texture by IUniform("texture0")
@ -194,7 +198,7 @@ class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(s
}
}
class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture_color.glsl"))) {
class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture_color.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform")
var texture by IUniform("texture0")
var color by F4Uniform("color")

View File

@ -83,12 +83,7 @@ class GLAttributeList(builder: Builder) {
companion object {
val VEC2F = Builder().push(GLType.VEC2F).build()
val VEC3F = Builder().push(GLType.VEC3F).build()
val VERTEX_TEXTURE = Builder().push(GLType.VEC3F).push(GLType.VEC2F).build()
val VERTEX_2D_TEXTURE = Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
val TILE = Builder().push(GLType.VEC3F, GLType.VEC2F, GLType.FLOAT).build()
}
}

View File

@ -3,7 +3,6 @@ package ru.dbotthepony.kstarbound.client.gl.vertex
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import java.io.Closeable
/**
* Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка
@ -13,7 +12,7 @@ class StreamVertexBuilder(
attributes: GLAttributeList,
type: GeometryType,
initialCapacity: Int = 64,
) : Closeable {
) {
val builder = VertexBuilder(attributes, type, initialCapacity)
private val vao = state.newVAO()
private val vbo = state.newVBO()
@ -44,12 +43,6 @@ class StreamVertexBuilder(
checkForGLError()
}
override fun close() {
vao.close()
vbo.close()
ebo.close()
}
fun singleSprite(x: Float, y: Float, width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) {
builder.begin()

View File

@ -1,99 +0,0 @@
package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
/**
* Служит для быстрой настройки состояния для будущей отрисовки
*
* Установка текстурных блоков, программы, самих текстур и загрузка юниформ должна осуществляться тут
*
* Класс обязан быть наследован для осмысленных результатов
*
* Ожидается, что состояние будет выставлено ПОЛНОСТЬЮ, т.е. НИКАКОЙ предыдущий код НЕ МОЖЕТ повлиять на результат выполнения
* шейдерной программы, которая связанна с этим объектом (за исключением не вызова [setTransform] внешним кодом)
*/
open class ConfiguredShaderProgram<T : GLShaderProgram>(
val program: T,
) {
private val transformLocation = program.getUniform("transform") as? GLShaderProgram.F4x4Uniform
open fun setTransform(value: Matrix4f) {
transformLocation?.value = value
}
/**
* Вызывается перед началом отрисовки
*/
open fun setup() {
program.use()
}
}
/**
* Запечённый статичный меш, позволяет быстро отрисовать меш со всеми параметрами
* с заданной матрицей трансформации
*/
class ConfiguredStaticMesh(
val programState: ConfiguredShaderProgram<*>,
val indexCount: Int,
val vao: VertexArrayObject,
val elementIndexType: Int,
) : AutoCloseable {
private var onClose = {}
constructor(programState: ConfiguredShaderProgram<*>, builder: VertexBuilder) : this(
programState,
builder.indexCount,
programState.program.state.newVAO(),
builder.indexType,
) {
val vbo = programState.program.state.newVBO()
val ebo = programState.program.state.newEBO()
onClose = {
vbo.close()
ebo.close()
}
vao.bind()
vbo.bind()
ebo.bind()
builder.upload(vbo, ebo, GL_STATIC_DRAW)
builder.attributes.apply(vao, true)
vao.unbind()
vbo.unbind()
ebo.unbind()
}
fun render(transform: Matrix4f? = null) {
check(isValid) { "$this is no longer valid" }
programState.setup()
if (transform != null) {
programState.setTransform(transform)
}
vao.bind()
glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L)
checkForGLError()
}
fun renderStacked(transform: Matrix4fStack) = render(transform.last())
var isValid = true
private set
override fun close() {
vao.close()
onClose.invoke()
isValid = false
}
}

View File

@ -244,7 +244,7 @@ class Font(
return size(breakLines(text))
}
private inner class Glyph(val char: Char) : AutoCloseable {
private inner class Glyph(val char: Char) {
private val texture: GLTexture2D?
val isEmpty: Boolean
@ -349,13 +349,5 @@ class Font(
stack.last().translateWithMultiplication(advanceX - bearingX, bearingY)
}
override fun close() {
vao?.close()
ebo?.close()
vbo?.close()
texture?.close()
}
}
}

View File

@ -3,7 +3,9 @@ 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.shader.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kvector.arrays.Matrix4f
class Mesh(state: GLStateTracker) {
constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) {
@ -39,5 +41,14 @@ class Mesh(state: GLStateTracker) {
vao.bind()
GL46.glDrawElements(GL46.GL_TRIANGLES, indexCount, indexType, 0L)
checkForGLError()
vao.unbind()
}
}
data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh(config.state)) {
fun render(transform: Matrix4f = config.state.matrixStack.last()) {
config.setup(transform)
mesh.render()
config.uninstall()
}
}

View File

@ -0,0 +1,29 @@
package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import java.util.stream.Stream
class MultiMeshBuilder {
data class Entry(val config: RenderConfig<*>, val builder: VertexBuilder, val layer: Int)
private val meshes = Reference2ObjectOpenHashMap<RenderConfig<*>, Int2ObjectOpenHashMap<Entry>>()
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
}
fun clear() = meshes.clear()
fun isEmpty() = meshes.isEmpty()
fun isNotEmpty() = meshes.isNotEmpty()
fun meshes(): Stream<Entry> {
return meshes.values.stream().flatMap { it.values.stream() }
}
}

View File

@ -0,0 +1,13 @@
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) {
open fun setup(transform: Matrix4f = state.matrixStack.last()) {
program.use()
}
open fun uninstall() {}
}

View File

@ -1,54 +1,21 @@
package ru.dbotthepony.kstarbound.client.render
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
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.gl.shader.GLTileProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i
import java.util.stream.Stream
import kotlin.collections.HashMap
data class TileLayer(
val program: ConfiguredShaderProgram<GLTileProgram>,
val vertices: VertexBuilder,
val zPos: Int
)
class TileLayerList {
private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectOpenHashMap<TileLayer>>()
/**
* Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
*
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
*/
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
return layers.computeIfAbsent(program) { Int2ObjectOpenHashMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
}).vertices
}
fun layers(): Stream<TileLayer> {
return layers.values.stream().flatMap { it.values.stream() }
}
fun clear() = layers.clear()
val isEmpty get() = layers.isEmpty()
val isNotEmpty get() = layers.isNotEmpty()
}
/**
* Хранит в себе программы для отрисовки определённых [TileDefinition]
*
@ -56,28 +23,29 @@ class TileLayerList {
*/
class TileRenderers(val client: StarboundClient) {
val state get() = client.gl
private val foregroundTilePrograms = HashMap<GLTexture2D, ForegroundTileProgram>()
private val backgroundTilePrograms = HashMap<GLTexture2D, BackgroundTileProgram>()
private val tileRenderersCache = HashMap<String, TileRenderer>()
private val modifierRenderersCache = HashMap<String, TileRenderer>()
fun getTileRenderer(defName: String): TileRenderer {
return tileRenderersCache.computeIfAbsent(defName) {
private val foreground = HashMap<GLTexture2D, Config>()
private val background = HashMap<GLTexture2D, Config>()
private val matCache = HashMap<String, TileRenderer>()
private val modCache = HashMap<String, TileRenderer>()
fun getMaterialRenderer(defName: String): TileRenderer {
return matCache.computeIfAbsent(defName) {
val def = client.starbound.tiles[defName] // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(this, def!!.value)
}
}
fun getModifierRenderer(defName: String): TileRenderer {
return modifierRenderersCache.computeIfAbsent(defName) {
return modCache.computeIfAbsent(defName) {
val def = client.starbound.tileModifiers[defName] // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(this, def!!.value)
}
}
private inner class ForegroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram<GLTileProgram>(state.programs.tile) {
override fun setup() {
super.setup()
private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(state, state.programs.tile) {
override fun setup(transform: Matrix4f) {
super.setup(transform)
state.activeTexture = 0
state.depthTest = false
program.texture = 0
@ -85,68 +53,19 @@ class TileRenderers(val client: StarboundClient) {
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST
program.color = FOREGROUND_COLOR
program.transform = transform
program.color = color
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other is ForegroundTileProgram) {
return texture == other.texture
}
return super.equals(other)
}
override fun hashCode(): Int {
return texture.hashCode()
}
override fun uninstall() {}
}
private inner class BackgroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram<GLTileProgram>(state.programs.tile) {
override fun setup() {
super.setup()
state.activeTexture = 0
state.depthTest = false
program.texture = 0
texture.bind()
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST
program.color = BACKGROUND_COLOR
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other is BackgroundTileProgram) {
return texture == other.texture
}
return super.equals(other)
}
override fun hashCode(): Int {
return texture.hashCode()
}
fun foreground(texture: GLTexture2D): RenderConfig<GLTileProgram> {
return foreground.computeIfAbsent(texture) { Config(it, FOREGROUND_COLOR) }
}
/**
* Возвращает запечённое состояние шейдера shaderVertexTexture с данной текстурой
*/
fun foreground(texture: GLTexture2D): ConfiguredShaderProgram<GLTileProgram> {
return foregroundTilePrograms.computeIfAbsent(texture, ::ForegroundTileProgram)
}
/**
* Возвращает запечённое состояние шейдера shaderVertexTextureRGBAColor с данной текстурой
*/
fun background(texture: GLTexture2D): ConfiguredShaderProgram<GLTileProgram> {
return backgroundTilePrograms.computeIfAbsent(texture, ::BackgroundTileProgram)
fun background(texture: GLTexture2D): RenderConfig<GLTileProgram> {
return background.computeIfAbsent(texture) { Config(it, BACKGROUND_COLOR) }
}
companion object {
@ -155,8 +74,6 @@ class TileRenderers(val client: StarboundClient) {
}
}
private fun vertexTextureBuilder() = VertexBuilder(GLAttributeList.TILE, GeometryType.QUADS)
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift
@ -236,7 +153,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
self: ITileState,
matchPiece: RenderMatch,
getter: ITileAccess,
layers: TileLayerList,
meshBuilder: MultiMeshBuilder,
pos: Vector2i,
thisBuilder: VertexBuilder,
background: Boolean,
@ -251,14 +168,14 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
renderers.foreground(state.loadTexture(renderPiece.piece.texture!!))
}
tesselateAt(self, renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset, isModifier)
tesselateAt(self, renderPiece.piece, getter, meshBuilder.get(program, def.renderParameters.zLevel), pos, renderPiece.offset, isModifier)
} else {
tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier)
}
}
for (subPiece in matchPiece.subMatches) {
val matched = tesselatePiece(self, subPiece, getter, layers, pos, thisBuilder, background, isModifier)
val matched = tesselatePiece(self, subPiece, getter, meshBuilder, pos, thisBuilder, background, isModifier)
if (matched == TestResult.HALT || matched == TestResult.CONTINUE && matchPiece.haltOnSubMatch) {
return TestResult.HALT
@ -280,20 +197,20 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
*
* [getter] Нужен для получения информации о ближайших блоках
*
* [layers] содержит текущие программы и их билдеры и их zPos
* [meshBuilder] содержит текущие программы и их билдеры и их zPos
*
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
*/
fun tesselate(self: ITileState, getter: ITileAccess, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: MultiMeshBuilder, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
val template = def.renderTemplate.value ?: return
val vertexBuilder = layers.computeIfAbsent(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel, ::vertexTextureBuilder)
val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel)
for ((_, matcher) in template.matches) {
for (matchPiece in matcher) {
val matched = tesselatePiece(self, matchPiece, getter, layers, pos, vertexBuilder, background, isModifier)
val matched = tesselatePiece(self, matchPiece, getter, meshBuilder, pos, vertexBuilder, background, isModifier)
if (matched == TestResult.HALT) {
break

View File

@ -7,14 +7,13 @@ import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.Vector2d
import java.io.Closeable
/**
* Базовый класс, отвечающий за отрисовку определённого ентити в мире
*
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
*/
open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) : Closeable {
open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) {
inline val state: GLStateTracker get() = client.gl
open val renderPos: Vector2d get() = entity.position
@ -30,10 +29,6 @@ open class EntityRenderer(val client: StarboundClient, val entity: Entity, open
open val layer: Int get() = Z_LEVEL_ENTITIES
override fun close() {
}
companion object {
/**
* Pseudo Z position for entities, for them to appear behind tile geometry,

View File

@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.io
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import ru.dbotthepony.kstarbound.api.IStarboundFile
import ru.dbotthepony.kstarbound.io.json.BinaryJsonReader
import java.io.BufferedInputStream