New shader render pipeline

This commit is contained in:
DBotThePony 2023-09-22 12:24:08 +07:00
parent 62dfc63839
commit 339891b6e2
Signed by: DBot
GPG Key ID: DCC23B5715498507
41 changed files with 955 additions and 932 deletions

View File

@ -83,7 +83,7 @@ dependencies {
implementation("com.github.jnr:jnr-ffi:2.2.13") implementation("com.github.jnr:jnr-ffi:2.2.13")
implementation("ru.dbotthepony:kbox2d:2.4.1.6") implementation("ru.dbotthepony:kbox2d:2.4.1.6")
implementation("ru.dbotthepony:kvector:2.8.0") implementation("ru.dbotthepony:kvector:2.9.0")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
} }

View File

@ -9,6 +9,7 @@ import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFWErrorCallback import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL15
import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GL45.*
import org.lwjgl.opengl.GLCapabilities import org.lwjgl.opengl.GLCapabilities
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
@ -33,9 +34,8 @@ import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.shader.GLShader
import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.input.UserInput
@ -49,22 +49,23 @@ import ru.dbotthepony.kstarbound.client.world.ClientWorld
import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.forEachValid
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.arrays.Matrix3fStack
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import ru.dbotthepony.kvector.vector.Vector3f
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.lang.ref.Cleaner import java.lang.ref.Cleaner
import java.lang.ref.WeakReference
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.Duration import java.time.Duration
@ -106,19 +107,13 @@ class StarboundClient : Closeable {
var clientTerminated = false var clientTerminated = false
private set private set
/** var viewportMatrixScreen: Matrix3f
* Матрица преобразования экранных координат (в пикселях) в нормализованные координаты
*/
var viewportMatrixScreen: Matrix4f
private set private set
get() = Matrix4f.unmodifiable(field) get() = Matrix3f.unmodifiable(field)
/** var viewportMatrixWorld: Matrix3f
* Матрица преобразования мировых координат в нормализованные координаты
*/
var viewportMatrixWorld: Matrix4f
private set private set
get() = Matrix4f.unmodifiable(field) get() = Matrix3f.unmodifiable(field)
var isRenderingGame = true var isRenderingGame = true
private set private set
@ -140,12 +135,10 @@ class StarboundClient : Closeable {
thread thread
} }
@Volatile var objectsCreated = 0L
var objectsCleaned = 0L
private set private set
@Volatile var objectsCleaned = 0L
var gcHits = 0L
private set private set
init { init {
@ -235,12 +228,19 @@ class StarboundClient : Closeable {
GLFW.glfwShowWindow(window) GLFW.glfwShowWindow(window)
putDebugLog("Initialized GLFW window") putDebugLog("Initialized GLFW window")
val v = glGenBuffers()
glBindBuffer(GL_ARRAY_BUFFER, v)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
GL15.glDeleteBuffers(v)
} }
val flat2DLines by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.LINES) } val maxTextureBlocks = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
val flat2DTriangles by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.TRIANGLES) } val maxVertexAttribBindPoints = glGetInteger(GL_MAX_VERTEX_ATTRIB_BINDINGS)
val flat2DTexturedQuads by lazy { StreamVertexBuilder(GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS) }
val quadWireframe by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME) } val stack = Matrix3fStack()
// минимальное время хранения 5 минут и... // минимальное время хранения 5 минут и...
private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder() private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder()
@ -260,24 +260,26 @@ class StarboundClient : Closeable {
} }
private val missingTexturePath = "/assetmissing.png" private val missingTexturePath = "/assetmissing.png"
private val regularShaderPrograms = ArrayList<WeakReference<GLShaderProgram.Regular>>()
val matrixStack = Matrix4fStack() fun addShaderProgram(program: GLShaderProgram) {
val freeType = FreeType() if (program is GLShaderProgram.Regular) {
val font = Font() regularShaderPrograms.add(WeakReference(program))
val box2dRenderer = Box2DRenderer() }
}
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable { fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
val cleanable = cleaner.register(ref) { objectsCreated++
objectsCleaned++
val cleanable = cleaner.register(ref) {
if (isSameThread()) { if (isSameThread()) {
objectsCleaned++
fn(nativeRef) fn(nativeRef)
checkForGLError() checkForGLError()
} else { } else {
gcHits++
synchronized(cleanerBacklog) { synchronized(cleanerBacklog) {
cleanerBacklog.add { cleanerBacklog.add {
objectsCleaned++
fn(nativeRef) fn(nativeRef)
checkForGLError() checkForGLError()
} }
@ -318,13 +320,10 @@ class StarboundClient : Closeable {
var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST) var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST)
var vbo by GLObjectTracker<BufferObject.VBO>(::glBindBuffer, GL_ARRAY_BUFFER)
var ebo by GLObjectTracker<BufferObject.EBO>(::glBindBuffer, GL_ELEMENT_ARRAY_BUFFER)
var vao by GLObjectTracker<VertexArrayObject>(::glBindVertexArray) var vao by GLObjectTracker<VertexArrayObject>(::glBindVertexArray)
var framebuffer by GLObjectTracker<GLFrameBuffer>(::glBindFramebuffer, GL_FRAMEBUFFER) var framebuffer by GLObjectTracker<GLFrameBuffer>(::glBindFramebuffer, GL_FRAMEBUFFER)
var program by GLObjectTracker<GLShaderProgram>(::glUseProgram) var program by GLObjectTracker<GLShaderProgram>(::glUseProgram)
val maxTextureBlocks = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
private val textures = Array(maxTextureBlocks) { GLObjectTracker<GLTexture2D>(GL11::glBindTexture, GL_TEXTURE_2D) } private val textures = Array(maxTextureBlocks) { GLObjectTracker<GLTexture2D>(GL11::glBindTexture, GL_TEXTURE_2D) }
var activeTexture = 0 var activeTexture = 0
@ -352,6 +351,9 @@ class StarboundClient : Closeable {
glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum) glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum)
} }
val freeType = FreeType()
val font = Font()
val box2dRenderer = Box2DRenderer()
val programs = GLPrograms() val programs = GLPrograms()
init { init {
@ -448,61 +450,24 @@ class StarboundClient : Closeable {
} }
} }
fun newVBO() = BufferObject.VBO()
fun newEBO() = BufferObject.EBO() fun newEBO() = BufferObject.EBO()
fun newVBO() = BufferObject.VBO()
fun newVAO() = VertexArrayObject() fun newVAO() = VertexArrayObject()
inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) { inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) {
val builder = quadWireframe val builder = programs.position.builder
builder.builder.begin() builder.builder.begin(GeometryType.QUADS_AS_LINES_WIREFRAME)
lambda.invoke(builder.builder) lambda.invoke(builder.builder)
builder.upload() builder.upload()
programs.flat.use() programs.position.use()
programs.flat.color = color programs.position.colorMultiplier = color
programs.flat.transform = matrixStack.last() programs.position.worldMatrix = stack.last()
builder.draw(GL_LINES) builder.draw(GL_LINES)
} }
inline fun quadColor(lambda: (VertexBuilder) -> Unit) {
val builder = programs.flatColor.builder
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
programs.flatColor.use()
programs.flatColor.transform = matrixStack.last()
builder.draw(GL_TRIANGLES)
}
inline fun quadTexture(texture: GLTexture2D, lambda: (VertexBuilder) -> Unit) {
val builder = programs.textured2d.builder
builder.builder.begin()
lambda.invoke(builder.builder)
builder.upload()
activeTexture = 0
texture.bind()
programs.textured2d.use()
programs.textured2d.transform = matrixStack.last()
programs.textured2d.texture = 0
builder.draw(GL_TRIANGLES)
}
inline fun quadWireframe(value: AABB, color: RGBAColor = RGBAColor.WHITE, chain: (VertexBuilder) -> Unit = {}) {
quadWireframe(color) {
it.quad(value.mins.x.toFloat(), value.mins.y.toFloat(), value.maxs.x.toFloat(), value.maxs.y.toFloat())
chain(it)
}
}
fun vertex(file: File) = GLShader(file, GL_VERTEX_SHADER) fun vertex(file: File) = GLShader(file, GL_VERTEX_SHADER)
fun fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER) fun fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER)
@ -533,12 +498,12 @@ class StarboundClient : Closeable {
} }
} }
private fun updateViewportMatrixScreen(): Matrix4f { private fun updateViewportMatrixScreen(): Matrix3f {
return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f)) return Matrix3f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat())
} }
private fun updateViewportMatrixWorld(): Matrix4f { private fun updateViewportMatrixWorld(): Matrix3f {
return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f) return Matrix3f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat())
} }
private val xMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer() private val xMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer()
@ -764,12 +729,14 @@ class StarboundClient : Closeable {
clearColor = RGBAColor.SLATE_GRAY clearColor = RGBAColor.SLATE_GRAY
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
matrixStack.clear(viewportMatrixWorld) stack.clear(Matrix3f.identity())
matrixStack.push().last() val viewMatrix = viewportMatrixWorld.copy()
.translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира .translate(viewportWidth / 2f, viewportHeight / 2f) // центр экрана + координаты отрисовки мира
.scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера .scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера
.translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере .translate(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере
regularShaderPrograms.forEachValid { it.viewMatrix = viewMatrix }
for (lambda in onPreDrawWorld) { for (lambda in onPreDrawWorld) {
lambda.invoke(layers) lambda.invoke(layers)
@ -786,7 +753,7 @@ class StarboundClient : Closeable {
layers = layers, layers = layers,
size = viewportRectangle) size = viewportRectangle)
layers.render(matrixStack) layers.render()
val viewportLightingMem = viewportLightingMem val viewportLightingMem = viewportLightingMem
@ -818,7 +785,7 @@ class StarboundClient : Closeable {
blendFunc = BlendFunc.MULTIPLY_BY_SRC blendFunc = BlendFunc.MULTIPLY_BY_SRC
quadTexture(viewportLightingTexture) { /*quadTexture(viewportLightingTexture) {
it.quad( it.quad(
(viewportCellX).toFloat(), (viewportCellX).toFloat(),
(viewportCellY).toFloat(), (viewportCellY).toFloat(),
@ -826,7 +793,7 @@ class StarboundClient : Closeable {
(viewportCellY + viewportCellHeight).toFloat(), (viewportCellY + viewportCellHeight).toFloat(),
QuadTransformers.uv() QuadTransformers.uv()
) )
} }*/
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
} }
@ -836,11 +803,9 @@ class StarboundClient : Closeable {
for (lambda in onPostDrawWorld) { for (lambda in onPostDrawWorld) {
lambda.invoke() lambda.invoke()
} }
matrixStack.pop()
} }
matrixStack.clear(viewportMatrixScreen) regularShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
val thisTime = System.currentTimeMillis() val thisTime = System.currentTimeMillis()
@ -851,22 +816,24 @@ class StarboundClient : Closeable {
alpha = (finishStartupRendering - thisTime) / 1000f alpha = (finishStartupRendering - thisTime) / 1000f
} }
matrixStack.push() stack.push()
matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat()) stack.last().translate(y = viewportHeight.toFloat())
var shade = 255 var shade = 255
for (i in startupTextList.size - 1 downTo 0) { for (i in startupTextList.size - 1 downTo 0) {
val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha))
matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f) stack.last().translate(y = -size.height * 1.2f)
if (shade > 120) { if (shade > 120) {
shade -= 10 shade -= 10
} }
} }
matrixStack.pop() stack.pop()
} }
stack.clear(Matrix3f.identity())
for (fn in onDrawGUI) { for (fn in onDrawGUI) {
fn.invoke() fn.invoke()
} }
@ -876,6 +843,7 @@ class StarboundClient : Closeable {
font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f) font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f)
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f) font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f)
font.render("OGL A: ${objectsCreated - objectsCleaned} D: $objectsCleaned", y = font.lineHeight * 1.8f, scale = 0.4f)
GLFW.glfwSwapBuffers(window) GLFW.glfwSwapBuffers(window)
GLFW.glfwPollEvents() GLFW.glfwPollEvents()

View File

@ -5,23 +5,26 @@ import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import java.nio.ByteBuffer import java.nio.ByteBuffer
sealed class BufferObject(val glType: Int) : GLObject() { sealed class BufferObject : GLObject() {
final override val client = StarboundClient.current() final override val client = StarboundClient.current()
final override val pointer = glGenBuffers() abstract val glType: Int
init { override fun bind() {
checkForGLError("Creating Vertex Buffer Object") // do nothing
client.registerCleanable(this, ::glDeleteBuffers, pointer)
} }
fun bufferData(data: ByteBuffer, usage: Int): BufferObject { override fun unbind() {
// do nothing
}
open fun bufferData(data: ByteBuffer, usage: Int): BufferObject {
client.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject { open fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject {
client.ensureSameThread() client.ensureSameThread()
if (length > data.remaining().toLong()) { if (length > data.remaining().toLong()) {
@ -34,51 +37,103 @@ sealed class BufferObject(val glType: Int) : GLObject() {
return this return this
} }
fun bufferData(data: IntArray, usage: Int): BufferObject { open fun bufferData(data: IntArray, usage: Int): BufferObject {
client.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: FloatArray, usage: Int): BufferObject { open fun bufferData(data: FloatArray, usage: Int): BufferObject {
client.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: DoubleArray, usage: Int): BufferObject { open fun bufferData(data: DoubleArray, usage: Int): BufferObject {
client.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
fun bufferData(data: LongArray, usage: Int): BufferObject { open fun bufferData(data: LongArray, usage: Int): BufferObject {
client.ensureSameThread() client.ensureSameThread()
glNamedBufferData(pointer, data, usage) glNamedBufferData(pointer, data, usage)
checkForGLError() checkForGLError()
return this return this
} }
class VBO : BufferObject(GL_ARRAY_BUFFER) { class EBO : BufferObject() {
override fun bind() { override val glType: Int
client.vbo = this get() = GL_ELEMENT_ARRAY_BUFFER
override val pointer: Int = glCreateBuffers()
init {
checkForGLError("Creating Element Buffer Object")
client.registerCleanable(this, ::glDeleteBuffers, pointer)
} }
override fun unbind() { override fun bufferData(data: ByteBuffer, usage: Int): EBO {
if (client.vbo == this) client.vbo = null return super.bufferData(data, usage) as EBO
}
override fun bufferData(data: ByteBuffer, usage: Int, length: Long): EBO {
return super.bufferData(data, usage, length) as EBO
}
override fun bufferData(data: IntArray, usage: Int): EBO {
return super.bufferData(data, usage) as EBO
}
override fun bufferData(data: FloatArray, usage: Int): EBO {
return super.bufferData(data, usage) as EBO
}
override fun bufferData(data: DoubleArray, usage: Int): EBO {
return super.bufferData(data, usage) as EBO
}
override fun bufferData(data: LongArray, usage: Int): EBO {
return super.bufferData(data, usage) as EBO
} }
} }
class EBO : BufferObject(GL_ELEMENT_ARRAY_BUFFER) { class VBO : BufferObject() {
override fun bind() { override val glType: Int
client.ebo = this get() = GL_ARRAY_BUFFER
override val pointer: Int = glCreateBuffers()
init {
checkForGLError("Creating Vertex Buffer Object")
client.registerCleanable(this, ::glDeleteBuffers, pointer)
} }
override fun unbind() { override fun bufferData(data: ByteBuffer, usage: Int): VBO {
if (client.ebo == this) client.ebo = null return super.bufferData(data, usage) as VBO
}
override fun bufferData(data: ByteBuffer, usage: Int, length: Long): VBO {
return super.bufferData(data, usage, length) as VBO
}
override fun bufferData(data: IntArray, usage: Int): VBO {
return super.bufferData(data, usage) as VBO
}
override fun bufferData(data: FloatArray, usage: Int): VBO {
return super.bufferData(data, usage) as VBO
}
override fun bufferData(data: DoubleArray, usage: Int): VBO {
return super.bufferData(data, usage) as VBO
}
override fun bufferData(data: LongArray, usage: Int): VBO {
return super.bufferData(data, usage) as VBO
} }
} }
} }

View File

@ -3,25 +3,27 @@ package ru.dbotthepony.kstarbound.client.gl
import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GL45.*
enum class GLType( enum class GLType(
val identity: Int, val type: Int,
val typeIndentity: Int, val elementType: Int,
val byteSize: Int, val byteSize: Int,
val logicalSize: Int, val elementSize: Int,
// location(position) width
val width: Int
) { ) {
INT(GL_INT, GL_INT, 4, 1), INT(GL_INT, GL_INT, 4, 1, 1),
UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1), UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1, 1),
FLOAT(GL_FLOAT, GL_FLOAT, 4, 1), FLOAT(GL_FLOAT, GL_FLOAT, 4, 1, 1),
DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1), DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1, 1),
VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2), VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2, 1),
VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3), VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3, 1),
VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4), VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4, 1),
VEC2I(GL_INT_VEC2, GL_INT, 8, 2), VEC2I(GL_INT_VEC2, GL_INT, 8, 2, 1),
VEC3I(GL_INT_VEC3, GL_INT, 12, 3), VEC3I(GL_INT_VEC3, GL_INT, 12, 3, 1),
VEC4I(GL_INT_VEC4, GL_INT, 16, 4), VEC4I(GL_INT_VEC4, GL_INT, 16, 4, 1),
MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2), MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2, 2),
MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3), MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3, 3),
MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4), MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4, 4),
} }

View File

@ -1,17 +1,39 @@
package ru.dbotthepony.kstarbound.client.gl package ru.dbotthepony.kstarbound.client.gl
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GL45.*
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import java.util.BitSet
class VertexArrayObject : GLObject() { class VertexArrayObject : GLObject() {
override val client = StarboundClient.current() override val client = StarboundClient.current()
override val pointer = glGenVertexArrays() override val pointer = glCreateVertexArrays()
init { init {
checkForGLError() checkForGLError("Creating Vertex Array Object")
client.registerCleanable(this, ::glDeleteVertexArrays, pointer) client.registerCleanable(this, ::glDeleteVertexArrays, pointer)
} }
private val enabledAttributes = BitSet()
private val boundBuffers = Int2ObjectAVLTreeMap<BindConfig>()
private data class BindConfig(val buffer: BufferObject, val stride: Int, val offset: Long)
var elementBuffer: BufferObject.EBO? = null
set(value) {
client.ensureSameThread()
if (field != value) {
if (value == null)
glVertexArrayElementBuffer(pointer, 0)
else
glVertexArrayElementBuffer(pointer, value.pointer)
checkForGLError()
field = value
}
}
override fun bind() { override fun bind() {
client.vao = this client.vao = this
} }
@ -21,18 +43,89 @@ class VertexArrayObject : GLObject() {
client.vao = null client.vao = null
} }
fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): VertexArrayObject { fun bindBufferToIndex(buffer: BufferObject.VBO, index: Int, vertexStride: Int, offsetFromBeginning: Long = 0L): VertexArrayObject {
client.ensureSameThread() client.ensureSameThread()
glVertexAttribPointer(position, size, type, normalize, stride, offset) require(index >= 0) { "Invalid bind index: $index" }
checkForGLError() require(index < client.maxVertexAttribBindPoints) { "Bind index is too big: $index; Max ${client.maxVertexAttribBindPoints}" }
val config = BindConfig(buffer, vertexStride, offsetFromBeginning)
if (boundBuffers[index] != config) {
glVertexArrayVertexBuffer(pointer, index, buffer.pointer, offsetFromBeginning, vertexStride)
checkForGLError()
boundBuffers[index] = config
}
return this return this
} }
fun unbindBufferFromIndex(index: Int): VertexArrayObject {
client.ensureSameThread()
require(index >= 0) { "Invalid attribute index: $index" }
require(index < client.maxVertexAttribBindPoints) { "Bind index is too big: $index; Max ${client.maxVertexAttribBindPoints}" }
if (index in boundBuffers) {
glVertexArrayVertexBuffer(pointer, index, 0, 0L, 0)
checkForGLError()
boundBuffers.remove(index)
}
return this
}
fun bindAttributeToIndex(attrib: Int, index: Int): VertexArrayObject {
client.ensureSameThread()
require(attrib >= 0) { "Invalid attribute index: $attrib" }
require(index >= 0) { "Invalid bind index: $index" }
require(index < client.maxVertexAttribBindPoints) { "Bind index is too big: $index; Max ${client.maxVertexAttribBindPoints}" }
glVertexArrayAttribBinding(pointer, attrib, index)
checkForGLError()
return this
}
fun attributeFormat(attrib: Int, type: GLType, normalized: Boolean, relativeOffset: Int): VertexArrayObject {
client.ensureSameThread()
require(attrib >= 0) { "Invalid attribute index: $attrib" }
glVertexArrayAttribFormat(pointer, attrib, type.elementSize, type.elementType, normalized, relativeOffset)
checkForGLError()
return this
}
fun bindAttributes(buffer: BufferObject.VBO, attributes: VertexAttributes, bufferOffset: Long = 0L) {
bindBufferToIndex(buffer, 0, attributes.vertexStride, bufferOffset)
for (attr in attributes.attributeList) {
bindAttributeToIndex(attr.index, 0)
attributeFormat(attr.index, attr.type.type, false, attr.relativeOffset)
enableAttribute(attr.index)
}
}
fun enableAttribute(position: Int): VertexArrayObject { fun enableAttribute(position: Int): VertexArrayObject {
client.ensureSameThread() client.ensureSameThread()
glEnableVertexArrayAttrib(pointer, position)
//glEnableVertexAttribArray(position) if (!enabledAttributes[position]) {
checkForGLError() glEnableVertexArrayAttrib(pointer, position)
checkForGLError()
enabledAttributes[position] = true
}
return this
}
fun disableAttribute(position: Int): VertexArrayObject {
client.ensureSameThread()
if (enabledAttributes[position]) {
glDisableVertexArrayAttrib(pointer, position)
checkForGLError()
enabledAttributes[position] = false
}
return this return this
} }
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.client.gl.shader package ru.dbotthepony.kstarbound.client.gl.shader
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL20.GL_COMPILE_STATUS import org.lwjgl.opengl.GL20.GL_COMPILE_STATUS
import org.lwjgl.opengl.GL20.glCompileShader import org.lwjgl.opengl.GL20.glCompileShader
import org.lwjgl.opengl.GL20.glCreateShader import org.lwjgl.opengl.GL20.glCreateShader
@ -33,9 +34,20 @@ class GLShader(body: String, type: Int) {
glGetShaderiv(pointer, GL_COMPILE_STATUS, result) glGetShaderiv(pointer, GL_COMPILE_STATUS, result)
if (result[0] == 0) { if (result[0] == 0) {
val split = body.split("\n")
val reps = if (split.size < 10) 1 else if (split.size < 100) 2 else if (split.size < 1000) 3 else 4
LOGGER.fatal("Next shader source has failed to compile, with line numbers:\n${split.withIndex().map {
"${it.index}${" ".repeat((reps - it.index.toString().length).coerceAtLeast(0))}: ${it.value}"
}.joinToString("\n")}")
throw ShaderCompilationException(glGetShaderInfoLog(pointer)) throw ShaderCompilationException(glGetShaderInfoLog(pointer))
} }
checkForGLError() checkForGLError()
} }
companion object {
private val LOGGER = LogManager.getLogger()
}
} }

View File

@ -8,7 +8,7 @@ import org.lwjgl.opengl.GL45.*
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLObject import ru.dbotthepony.kstarbound.client.gl.GLObject
import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kvector.api.IStruct2f import ru.dbotthepony.kvector.api.IStruct2f
import ru.dbotthepony.kvector.api.IStruct3f import ru.dbotthepony.kvector.api.IStruct3f
import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.api.IStruct4f
@ -26,8 +26,15 @@ import kotlin.reflect.KProperty
open class GLShaderProgram( open class GLShaderProgram(
shaders: Iterable<GLShader>, shaders: Iterable<GLShader>,
val attributes: GLAttributeList val attributes: VertexAttributes
) : GLObject() { ) : GLObject() {
interface Regular {
var viewMatrix: Matrix3f // set before rendering anything (camera and/or projection)
var worldMatrix: Matrix3f // global matrix stack
var modelMatrix: Matrix3f // should be set by drawing code itself
var colorMultiplier: IStruct4f
}
final override val client = StarboundClient.current() final override val client = StarboundClient.current()
final override val pointer = glCreateProgram() final override val pointer = glCreateProgram()
@ -42,14 +49,12 @@ open class GLShaderProgram(
glLinkProgram(pointer) glLinkProgram(pointer)
val success = intArrayOf(0) if (glGetProgrami(pointer, GL_LINK_STATUS) == 0)
glGetProgramiv(pointer, GL_LINK_STATUS, success)
if (success[0] == 0) {
throw ShaderLinkException(glGetProgramInfoLog(pointer)) throw ShaderLinkException(glGetProgramInfoLog(pointer))
}
glGetError() glGetError()
client.addShaderProgram(this)
} }
fun use(): GLShaderProgram { fun use(): GLShaderProgram {
@ -82,16 +87,17 @@ open class GLShaderProgram(
return locations.computeIfAbsent(name, Object2ObjectFunction { IUniform(name) }) as? IUniform ?: throw IllegalStateException("Uniform $name has type of ${locations[name]!!::class.simpleName}") return locations.computeIfAbsent(name, Object2ObjectFunction { IUniform(name) }) as? IUniform ?: throw IllegalStateException("Uniform $name has type of ${locations[name]!!::class.simpleName}")
} }
abstract inner class Uniform<V : Any>(val name: String) : ReadWriteProperty<Any?, V> { abstract inner class Uniform<V : Any>(val name: String, allowMissing: Boolean = false) : ReadWriteProperty<Any?, V> {
init { init {
client.ensureSameThread() client.ensureSameThread()
require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" } require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" }
} }
val location = glGetUniformLocation(pointer, name) val location = glGetUniformLocation(pointer, name)
val isMissing = location == -1
init { init {
if (location == -1) if (isMissing && !allowMissing)
throw NoSuchElementException("Program ${this@GLShaderProgram} does not have uniform with name $name") throw NoSuchElementException("Program ${this@GLShaderProgram} does not have uniform with name $name")
locations[name] = this locations[name] = this
@ -113,10 +119,11 @@ open class GLShaderProgram(
} }
} }
inner class FUniform(name: String) : Uniform<Float>(name) { inner class FUniform(name: String, allowMissing: Boolean = false) : Uniform<Float>(name, allowMissing) {
override var value: Float = 0f override var value: Float = 0f
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
if (field != value) { if (field != value) {
glProgramUniform1f(pointer, location, value) glProgramUniform1f(pointer, location, value)
@ -135,13 +142,14 @@ open class GLShaderProgram(
} }
} }
inner class F2Uniform(name: String) : Uniform<IStruct2f>(name) { inner class F2Uniform(name: String, allowMissing: Boolean = false) : Uniform<IStruct2f>(name, allowMissing) {
private var v0 = 0f private var v0 = 0f
private var v1 = 0f private var v1 = 0f
override var value: IStruct2f = Vector2f.ZERO override var value: IStruct2f = Vector2f.ZERO
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
val (v0, v1) = value val (v0, v1) = value
@ -156,7 +164,7 @@ open class GLShaderProgram(
} }
} }
inner class F3Uniform(name: String) : Uniform<IStruct3f>(name) { inner class F3Uniform(name: String, allowMissing: Boolean = false) : Uniform<IStruct3f>(name, allowMissing) {
private var v0 = 0f private var v0 = 0f
private var v1 = 0f private var v1 = 0f
private var v2 = 0f private var v2 = 0f
@ -164,6 +172,7 @@ open class GLShaderProgram(
override var value: IStruct3f = Vector3f.ZERO override var value: IStruct3f = Vector3f.ZERO
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
val (v0, v1, v2) = value val (v0, v1, v2) = value
@ -182,10 +191,11 @@ open class GLShaderProgram(
private val buff3x3: FloatBuffer = ByteBuffer.allocateDirect(4 * 3 * 3).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer() private val buff3x3: FloatBuffer = ByteBuffer.allocateDirect(4 * 3 * 3).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer()
private val buff4x4: FloatBuffer = ByteBuffer.allocateDirect(4 * 4 * 4).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer() private val buff4x4: FloatBuffer = ByteBuffer.allocateDirect(4 * 4 * 4).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer()
inner class F3x3Uniform(name: String) : Uniform<Matrix3f>(name) { inner class F3x3Uniform(name: String, allowMissing: Boolean = false) : Uniform<Matrix3f>(name, allowMissing) {
override var value: Matrix3f = Matrix3f.zero() override var value: Matrix3f = Matrix3f.zero()
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
if (field != value) { if (field != value) {
buff3x3.position(0) buff3x3.position(0)
@ -199,15 +209,16 @@ open class GLShaderProgram(
} }
} }
inner class F4x4Uniform(name: String) : Uniform<Matrix4f>(name) { inner class F4x4Uniform(name: String, allowMissing: Boolean = false) : Uniform<Matrix4f>(name, allowMissing) {
private val _value = ByteBuffer.allocate(4 * 4 * 4).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer() private val _value = ByteBuffer.allocate(4 * 4 * 4).also { it.order(ByteOrder.nativeOrder()) }.asFloatBuffer()
override var value: Matrix4f = Matrix4f.zero() override var value: Matrix4f = Matrix4f.zero()
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
buff4x4.position(0) buff4x4.position(0)
value.storeRowColumn(buff4x4) value.storeColumnRow(buff4x4)
buff4x4.position(0) buff4x4.position(0)
_value.position(0) _value.position(0)
@ -221,7 +232,7 @@ open class GLShaderProgram(
} }
} }
inner class F4Uniform(name: String) : Uniform<IStruct4f>(name) { inner class F4Uniform(name: String, allowMissing: Boolean = false) : Uniform<IStruct4f>(name, allowMissing) {
private var v0 = 0f private var v0 = 0f
private var v1 = 0f private var v1 = 0f
private var v2 = 0f private var v2 = 0f
@ -230,6 +241,7 @@ open class GLShaderProgram(
override var value: IStruct4f = Vector4f.ZERO override var value: IStruct4f = Vector4f.ZERO
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
val (v0, v1, v2, v3) = value val (v0, v1, v2, v3) = value
@ -246,10 +258,11 @@ open class GLShaderProgram(
} }
} }
inner class IUniform(name: String) : Uniform<Int>(name) { inner class IUniform(name: String, allowMissing: Boolean = false) : Uniform<Int>(name, allowMissing) {
override var value: Int = 0 override var value: Int = 0
set(value) { set(value) {
client.ensureSameThread() client.ensureSameThread()
if (isMissing) return
if (field != value) { if (field != value) {
glProgramUniform1i(pointer, location, value) glProgramUniform1i(pointer, location, value)

View File

@ -1,125 +1,50 @@
package ru.dbotthepony.kstarbound.client.gl.shader package ru.dbotthepony.kstarbound.client.gl.shader
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLType
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
private fun internalVertex(string: String) = StarboundClient.current().internalVertex(string)
private fun internalFragment(string: String) = StarboundClient.current().internalFragment(string)
private fun shaders(name: String): List<GLShader> { private fun shaders(name: String): List<GLShader> {
val client = StarboundClient.current() val client = StarboundClient.current()
return listOf(client.internalVertex("shaders/$name.vsh"), client.internalFragment("shaders/$name.fsh")) return listOf(client.internalVertex("shaders/$name.vsh"), client.internalFragment("shaders/$name.fsh"))
} }
class GLLiquidProgram : GLShaderProgram(shaders("liquid"), FORMAT) { private fun shaders1(name: String): List<GLShader> {
val client = StarboundClient.current()
return listOf(client.internalVertex("shaders/vertex/$name.vsh"), client.internalFragment("shaders/fragment/$name.fsh"))
}
class FontProgram : GLShaderProgram(shaders1("font"), VertexAttributes.POSITION_UV), GLShaderProgram.Regular {
override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix", true)
override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix", true)
override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix", true)
override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier", true)
var texture by IUniform("texture0")
val builder = StreamVertexBuilder(attributes, GeometryType.QUADS)
init {
viewMatrix = Matrix3f.identity()
modelMatrix = Matrix3f.identity()
colorMultiplier = RGBAColor.WHITE
}
}
class GLLiquidProgram : GLShaderProgram(shaders("liquid"), VertexAttributes.POSITION) {
var baselineColor by F4Uniform("baselineColor") var baselineColor by F4Uniform("baselineColor")
var transform by F4x4Uniform("transform") var transform by F3x3Uniform("transform")
val builder = StreamVertexBuilder(attributes, GeometryType.QUADS)
val builder by lazy {
StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build()
}
}
class GLFlatColorProgram : GLShaderProgram(shaders("flat_color"), FORMAT) {
var transform by F4x4Uniform("transform")
val builder by lazy {
StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC4F).build()
}
}
class GLTileProgram : GLShaderProgram(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 : GLShaderProgram(shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) {
var transform by F4x4Uniform("transform")
var color by F4Uniform("color")
var texture by IUniform("texture0")
init {
transform = Matrix4f.identity()
color = RGBAColor.WHITE
}
}
class GLFlatProgram : GLShaderProgram(shaders("flat"), GLAttributeList.VEC2F) {
var transform by F4x4Uniform("transform")
var color by F4Uniform("color")
init {
color = RGBAColor.WHITE
}
}
class GLTexturedProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/texture.glsl"), internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform")
var texture by IUniform("texture0")
val builder by lazy {
StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F).build(), GeometryType.QUADS, 16384)
}
init {
transform = Matrix4f.identity()
}
}
class GLTextured2dProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/2dtexture.glsl"), internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform")
var texture by IUniform("texture0")
val builder by lazy {
StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC2F, GLType.VEC2F).build(), GeometryType.QUADS, 16384)
}
init {
transform = Matrix4f.identity()
}
}
class GLTexturedColoredProgram : GLShaderProgram(listOf(internalVertex("shaders/vertex/texture.glsl"), internalFragment("shaders/fragment/texture_color.glsl")), GLAttributeList.VERTEX_TEXTURE) {
var transform by F4x4Uniform("transform")
var texture by IUniform("texture0")
var color by F4Uniform("color")
init {
transform = Matrix4f.identity()
color = RGBAColor.WHITE
}
} }
class GLPrograms { class GLPrograms {
val tile = GLTileProgram() val position = UberShader.Builder().build()
val font = GLFontProgram() val positionTexture = UberShader.Builder().withTexture().build()
val flat = GLFlatProgram() val positionColor = UberShader.Builder().withColor().build()
val flatColor = GLFlatColorProgram() val tile = UberShader.Builder().withTexture().withHueShift().build()
val font = FontProgram()
val liquid = GLLiquidProgram() val liquid = GLLiquidProgram()
val textured = GLTexturedProgram()
val textured2d = GLTextured2dProgram()
val texturedColored = GLTexturedColoredProgram()
} }

View File

@ -0,0 +1,84 @@
package ru.dbotthepony.kstarbound.client.gl.shader
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER
import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributeType
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor
class UberShader private constructor(fragment: GLShader, vertex: GLShader, attributes: VertexAttributes) : GLShaderProgram(listOf(fragment, vertex), attributes), GLShaderProgram.Regular {
override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix")
override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix")
override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix")
override var colorMultiplier: IStruct4f by F4Uniform("colorMultiplier")
var texture0 by IUniform("texture0", VertexAttributeType.UV !in attributes)
val builder = StreamVertexBuilder(attributes)
init {
viewMatrix = Matrix3f.identity()
worldMatrix = Matrix3f.identity()
modelMatrix = Matrix3f.identity()
colorMultiplier = RGBAColor.WHITE
}
class Builder {
private val directives = Object2ObjectArrayMap<String, String>()
private val attributes = ObjectArraySet<VertexAttributeType>()
init {
attributes.add(VertexAttributeType.POSITION)
}
private var nextAttributeIndex = 1
private fun attribute(type: VertexAttributeType, name: String = type.name): Builder {
if (attributes.add(type)) {
directives[name] = nextAttributeIndex.toString()
nextAttributeIndex += type.type.width
}
return this
}
fun withTexture(): Builder {
return attribute(VertexAttributeType.UV, "TEXTURE")
}
fun withColor(): Builder {
return attribute(VertexAttributeType.COLOR)
}
fun withHueShift(): Builder {
return attribute(VertexAttributeType.HUE_SHIFT)
}
fun build(): UberShader {
val client = StarboundClient.current()
val fragment = GLShader("#version 450\n" +
directives.entries.joinToString("\n") { "#define ${it.key}" + (if (it.value != "") " " + it.value else "") } +
fragment, GL_FRAGMENT_SHADER)
val vertex = GLShader("#version 450\n" +
directives.entries.joinToString("\n") { "#define ${it.key}" + (if (it.value != "") " " + it.value else "") } +
vertex, GL_VERTEX_SHADER)
val attributes = VertexAttributes.of(attributes)
return UberShader(fragment, vertex, attributes)
}
}
companion object {
private val fragment by lazy { StarboundClient.readInternal("shaders/fragment/configurable.fsh") }
private val vertex by lazy { StarboundClient.readInternal("shaders/vertex/configurable.vsh") }
}
}

View File

@ -1,89 +0,0 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.client.gl.GLType
import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject
/**
* Хранит список аттрибутов для применения к Vertex Array Object
*
* Аттрибуты плотно упакованы и идут один за другим
*
* Создаётся через [GLAttributeList.Builder]
*/
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
/**
* Шаг данных аттрибутов, в байтах. Т.е. одна полная вершина будет занимать [stride] байт в памяти.
*/
val stride: Int
operator fun get(index: Int) = attributes[index]
init {
val buildList = ArrayList<Attribute>()
var offset = 0L
var stride = 0
for (i in builder.attributes) {
stride += i.second.byteSize
}
this.stride = stride
for (i in builder.attributes.indices) {
val value = builder.attributes[i].second
buildList.add(Attribute(builder.attributes[i].first, i, value, stride, offset))
offset += value.byteSize
}
attributes = ImmutableList.copyOf(buildList)
}
/**
* Применяет список атрибутов к заданному [VertexArrayObject] (попутно включая или отключая их через [enable])
*/
fun apply(target: VertexArrayObject, 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)
if (enable) {
target.enableAttribute(i)
}
}
}
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 = Builder().push(GLType.VEC2F).build()
val VERTEX_TEXTURE = Builder().push(GLType.VEC3F).push(GLType.VEC2F).build()
val VERTEX_2D_TEXTURE = Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}

View File

@ -17,15 +17,10 @@ enum class GeometryType(
LINES(2, IntList.of(0, 1)), LINES(2, IntList.of(0, 1)),
TRIANGLES(3, IntList.of(0, 1, 2)), TRIANGLES(3, IntList.of(0, 1, 2)),
/**
* A B C B C D
*/
QUADS(4, IntList.of(0, 1, 2, 1, 2, 3)),
/** /**
* A B C C D A * A B C C D A
*/ */
QUADS_ALTERNATIVE(4, IntList.of(0, 1, 2, 2, 3, 0)), QUADS(4, IntList.of(0, 1, 2, 2, 3, 0)),
QUADS_AS_LINES(4, IntList.of(0, 1, 0, 2, 1, 3, 2, 3)), QUADS_AS_LINES(4, IntList.of(0, 1, 0, 2, 1, 3, 2, 3)),
QUADS_AS_LINES_WIREFRAME(4, IntList.of(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)), QUADS_AS_LINES_WIREFRAME(4, IntList.of(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)),

View File

@ -1,71 +0,0 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import ru.dbotthepony.kstarbound.defs.image.IUVCoordinates
import ru.dbotthepony.kstarbound.defs.image.UVCoordinates
import ru.dbotthepony.kvector.vector.RGBAColor
typealias QuadVertexTransformer = (VertexBuilder, Int) -> VertexBuilder
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(uv: IUVCoordinates): QuadVertexTransformer {
return uv(uv.u0, uv.v0, uv.u1, uv.v1)
}
fun uv(uv: IUVCoordinates, lambda: QuadVertexTransformer): QuadVertexTransformer {
return uv(uv.u0, uv.v0, uv.u1, uv.v1, lambda)
}
fun vec4(x: Float, y: Float, z: Float, w: Float, after: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): QuadVertexTransformer {
return transformer@{ it, index ->
it.pushVec4f(x, y, z, w)
return@transformer after(it, index)
}
}
}

View File

@ -10,8 +10,8 @@ import ru.dbotthepony.kstarbound.client.gl.checkForGLError
* Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка * Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка
*/ */
class StreamVertexBuilder( class StreamVertexBuilder(
attributes: GLAttributeList, attributes: VertexAttributes,
type: GeometryType, type: GeometryType? = null,
initialCapacity: Int = 64, initialCapacity: Int = 64,
) { ) {
val state = StarboundClient.current() val state = StarboundClient.current()
@ -21,15 +21,8 @@ class StreamVertexBuilder(
private val ebo = BufferObject.EBO() private val ebo = BufferObject.EBO()
init { init {
vao.bind() vao.elementBuffer = ebo
vbo.bind() vao.bindAttributes(vbo, attributes)
ebo.bind()
attributes.apply(vao, true)
vao.unbind()
vbo.unbind()
ebo.unbind()
} }
fun upload(drawType: Int = GL45.GL_DYNAMIC_DRAW) { fun upload(drawType: Int = GL45.GL_DYNAMIC_DRAW) {
@ -43,22 +36,6 @@ class StreamVertexBuilder(
bind() bind()
GL45.glDrawElements(primitives, builder.indexCount, builder.indexType, 0L) GL45.glDrawElements(primitives, builder.indexCount, builder.indexType, 0L)
checkForGLError() checkForGLError()
} unbind()
fun singleSprite(x: Float, y: Float, width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) {
builder.begin()
builder.quadRotatedZ(x, y, width, height, z, 0f, 0f, angle, transformer)
upload()
draw()
}
fun singleSprite(width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) {
singleSprite(-width / 2f, -height / 2f, width / 2f, height / 2f, z, angle, transformer)
}
fun singleSprite(width: Float, height: Float, angle: Double = 0.0, transformer: QuadVertexTransformer) {
singleSprite(-width / 2f, -height / 2f, width / 2f, height / 2f, 0f, angle, transformer)
} }
} }

View File

@ -0,0 +1,10 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import ru.dbotthepony.kstarbound.client.gl.GLType
enum class VertexAttributeType(val type: GLType) {
POSITION(GLType.VEC2F),
COLOR(GLType.VEC4F),
UV(GLType.VEC2F),
HUE_SHIFT(GLType.FLOAT)
}

View File

@ -0,0 +1,70 @@
package ru.dbotthepony.kstarbound.client.gl.vertex
import com.google.common.collect.ImmutableList
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import java.util.Collections
import java.util.EnumMap
import java.util.EnumSet
class VertexAttributes private constructor(
val attributeList: ImmutableList<Attribute>,
val attributeMap: Map<VertexAttributeType, Attribute>,
val vertexStride: Int,
val allFlags: Long,
) {
data class Attribute(val type: VertexAttributeType, val index: Int, val relativeOffset: Int, val flag: Long)
val size get() = attributeList.size
operator fun get(index: Int): Attribute = attributeList[index]
operator fun get(index: VertexAttributeType): Attribute? = attributeMap[index]
operator fun contains(index: VertexAttributeType) = index in attributeMap
companion object {
val POSITION = of(VertexAttributeType.POSITION)
val POSITION_UV = of(VertexAttributeType.POSITION, VertexAttributeType.UV)
val POSITION_COLOR = of(VertexAttributeType.POSITION, VertexAttributeType.COLOR)
@JvmStatic
fun of(vararg attributes: VertexAttributeType): VertexAttributes {
return of(ObjectArrayList.wrap(attributes))
}
@JvmStatic
fun of(attributes: Collection<VertexAttributeType>): VertexAttributes {
val dup = EnumSet.noneOf(VertexAttributeType::class.java)
attributes.forEach {
if (!dup.add(it)) {
throw IllegalArgumentException("Duplicate vertex attribute: $it")
}
}
require(attributes.size < 64) { "Vertex attribute list is insanely big: ${attributes.size}; 64 is max" }
val attributeList = ImmutableList.Builder<Attribute>()
val attributeMap = EnumMap<VertexAttributeType, Attribute>(VertexAttributeType::class.java)
var allVertexFlags = 0L
var stride = 0
for (attr in attributes) {
stride += attr.type.byteSize
allVertexFlags = (allVertexFlags shl 1) or 1L
}
var offset = 0
var index = 0
for ((i, attr) in attributes.withIndex()) {
val constructed = Attribute(attr, index, offset, 1L shl i)
index += attr.type.width
attributeList.add(constructed)
offset += attr.type.byteSize
attributeMap[attr] = constructed
}
return VertexAttributes(attributeList.build(), Collections.unmodifiableMap(attributeMap), stride, allVertexFlags)
}
}
}

View File

@ -4,13 +4,11 @@ import org.lwjgl.opengl.GL45
import org.lwjgl.opengl.GL45.GL_UNSIGNED_INT import org.lwjgl.opengl.GL45.GL_UNSIGNED_INT
import org.lwjgl.opengl.GL45.GL_UNSIGNED_SHORT import org.lwjgl.opengl.GL45.GL_UNSIGNED_SHORT
import org.lwjgl.opengl.GL45.GL_UNSIGNED_BYTE import org.lwjgl.opengl.GL45.GL_UNSIGNED_BYTE
import ru.dbotthepony.kstarbound.client.gl.GLType
import ru.dbotthepony.kstarbound.client.gl.BufferObject import ru.dbotthepony.kstarbound.client.gl.BufferObject
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.api.IStruct2f
import ru.dbotthepony.kvector.api.IStruct4f
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import kotlin.math.cos
import kotlin.math.sin
private fun interface IndexWriter { private fun interface IndexWriter {
fun write(buffer: ByteBuffer, value: Int) fun write(buffer: ByteBuffer, value: Int)
@ -50,11 +48,11 @@ private fun indexSize(type: Int): Int {
* Загрузка в память видеокарты происходит напрямую из буферов, через метод [upload] * Загрузка в память видеокарты происходит напрямую из буферов, через метод [upload]
*/ */
class VertexBuilder( class VertexBuilder(
val attributes: GLAttributeList, val attributes: VertexAttributes,
val defaultMode: GeometryType? = null, val defaultMode: GeometryType? = null,
initialCapacity: Int = 64 initialCapacity: Int = 64
) { ) {
constructor(attributes: GLAttributeList, initialCapacity: Int) : this(attributes, null, initialCapacity) constructor(attributes: VertexAttributes, initialCapacity: Int) : this(attributes, null, initialCapacity)
init { init {
require(initialCapacity > 0) { "Invalid capacity: $initialCapacity" } require(initialCapacity > 0) { "Invalid capacity: $initialCapacity" }
@ -73,14 +71,13 @@ class VertexBuilder(
var indexSize: Int = indexSize(indexType) var indexSize: Int = indexSize(indexType)
private set private set
private var vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.stride).also { it.order(ByteOrder.LITTLE_ENDIAN) } private var vertexMemory: ByteBuffer = ByteBuffer.allocateDirect(vertexCapacity * attributes.vertexStride).also { it.order(ByteOrder.LITTLE_ENDIAN) }
private var indexMemory = ByteBuffer.allocateDirect(indexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) } private var indexMemory: ByteBuffer = ByteBuffer.allocateDirect(indexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) }
private var indexWriter = writer(indexType) private var indexWriter = writer(indexType)
private var inVertex = false private var inVertex = false
private var attributeIndex = 0 private var vertexAttributes = 0L
private var elementIndexOffset = 0 private var elementIndexOffset = 0
private var elementVertices = 0 private var elementVertices = 0
var vertexCount = 0 // distinct vertices var vertexCount = 0 // distinct vertices
@ -113,7 +110,7 @@ class VertexBuilder(
val indexSize = indexSize(indexType) val indexSize = indexSize(indexType)
val vertexPos = vertexMemory.position() val vertexPos = vertexMemory.position()
val vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.stride).also { it.order(ByteOrder.LITTLE_ENDIAN) } val vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.vertexStride).also { it.order(ByteOrder.LITTLE_ENDIAN) }
this.vertexMemory.position(0) this.vertexMemory.position(0)
vertexMemory.put(this.vertexMemory) vertexMemory.put(this.vertexMemory)
@ -171,7 +168,13 @@ class VertexBuilder(
} }
fun mode(mode: GeometryType): VertexBuilder { fun mode(mode: GeometryType): VertexBuilder {
check(!inVertex) { "Can't change buffer geometry type during vertex construction" } if (inVertex) {
if (vertexAttributes != attributes.allFlags)
throw IllegalStateException("Can't change buffer geometry type during vertex construction")
else
end()
}
check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" } check(elementVertices == 0) { "Can't change buffer geometry type while not having fully built current element" }
this.mode = mode this.mode = mode
ensureCapacity() ensureCapacity()
@ -183,8 +186,8 @@ class VertexBuilder(
fun begin(mode: GeometryType? = defaultMode): VertexBuilder { fun begin(mode: GeometryType? = defaultMode): VertexBuilder {
this.mode = mode this.mode = mode
inVertex = false inVertex = false
attributeIndex = 0
elementIndexOffset = 0 elementIndexOffset = 0
vertexAttributes = 0L
vertexCount = 0 vertexCount = 0
indexCount = 0 indexCount = 0
elementCount = 0 elementCount = 0
@ -194,33 +197,7 @@ class VertexBuilder(
return this return this
} }
private fun checkBounds() { fun upload(vertices: BufferObject.VBO, elements: BufferObject.EBO, drawType: Int = GL45.GL_DYNAMIC_DRAW) {
if (attributeIndex >= attributes.size) {
throw IndexOutOfBoundsException("Tried to add new attribute when already added all attributes")
}
}
fun expect(type: GLType): VertexBuilder {
checkBounds()
if (attributes[attributeIndex].glType != type) {
throw IllegalStateException("Expected attribute type $type, got ${attributes[attributeIndex].name}[${attributes[attributeIndex].glType}] (at position $attributeIndex)")
}
return this
}
fun expect(name: String): VertexBuilder {
checkBounds()
if (attributes[attributeIndex].name != name) {
throw IllegalStateException("Expected attribute name $name, got ${attributes[attributeIndex].name}[${attributes[attributeIndex].glType}] (at position $attributeIndex)")
}
return this
}
fun upload(vbo: BufferObject.VBO, ebo: BufferObject.EBO, drawType: Int = GL45.GL_DYNAMIC_DRAW) {
end() end()
check(elementVertices == 0) { "Not fully built vertex element ($mode requires ${mode?.elements} vertex points to be present, yet last strip has only $elementVertices elements)" } check(elementVertices == 0) { "Not fully built vertex element ($mode requires ${mode?.elements} vertex points to be present, yet last strip has only $elementVertices elements)" }
@ -231,8 +208,8 @@ class VertexBuilder(
vertexMemory.position(0) vertexMemory.position(0)
indexMemory.position(0) indexMemory.position(0)
vbo.bufferData(vertexMemory, drawType, length = vertexPos.toLong()) vertices.bufferData(vertexMemory, drawType, length = vertexPos.toLong())
ebo.bufferData(indexMemory, drawType, length = elementPos.toLong()) elements.bufferData(indexMemory, drawType, length = elementPos.toLong())
vertexMemory.position(vertexPos) vertexMemory.position(vertexPos)
indexMemory.position(elementPos) indexMemory.position(elementPos)
@ -244,8 +221,11 @@ class VertexBuilder(
val mode = mode!! val mode = mode!!
inVertex = false inVertex = false
if (attributeIndex != attributes.size) { if (vertexAttributes != attributes.allFlags) {
throw IllegalStateException("Unfinished vertex, we are at $attributeIndex, while we have ${attributes.size} attributes") val missing = ArrayList<VertexAttributeType>()
attributes.attributeList.forEach { if (it.flag and vertexAttributes == 0L) missing.add(it.type) }
throw IllegalStateException("Unfinished vertex, missing ${missing.joinToString()} attributes")
} }
vertexCount++ vertexCount++
@ -271,171 +251,63 @@ class VertexBuilder(
checkNotNull(mode) { "No builder mode is set" } checkNotNull(mode) { "No builder mode is set" }
inVertex = true inVertex = true
attributeIndex = 0 vertexAttributes = 0L
return this return this
} }
fun pushVec4f(x: Float, y: Float, z: Float, w: Float): VertexBuilder { fun vertex(x: Float, y: Float): VertexBuilder {
expect(GLType.VEC4F)
val memory = vertexMemory
memory.putFloat(x)
memory.putFloat(y)
memory.putFloat(z)
memory.putFloat(w)
attributeIndex++
return this
}
fun pushVec3f(x: Float, y: Float, z: Float): VertexBuilder {
expect(GLType.VEC3F)
val memory = vertexMemory
memory.putFloat(x)
memory.putFloat(y)
memory.putFloat(z)
attributeIndex++
return this
}
fun pushVec2f(x: Float, y: Float): VertexBuilder {
expect(GLType.VEC2F)
val memory = vertexMemory
memory.putFloat(x)
memory.putFloat(y)
attributeIndex++
return this
}
fun push(value: Float): VertexBuilder {
expect(GLType.FLOAT)
vertexMemory.putFloat(value)
attributeIndex++
return this
}
fun shadowLine(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder {
vertex() vertex()
pushVec4f(x0, y0, x1, y1) position(x, y)
pushVec2f(0f, 0f) return this
}
vertex() private fun pushFloat(attr: VertexAttributes.Attribute, x: Float): VertexBuilder {
pushVec4f(x0, y0, x1, y1) vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
pushVec2f(0f, 1f) vertexMemory.putFloat(x)
vertexAttributes = vertexAttributes or attr.flag
vertex()
pushVec4f(x0, y0, x1, y1)
pushVec2f(1f, 1f)
vertex()
pushVec4f(x0, y0, x1, y1)
pushVec2f(1f, 0f)
return this return this
} }
fun dfShadowLine(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder { private fun pushFloat2(attr: VertexAttributes.Attribute, x: Float, y: Float): VertexBuilder {
shadowLine(x0, y0, x1, y1) vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
shadowLine(x1, y1, x0, y0) vertexMemory.putFloat(x)
return this vertexMemory.putFloat(y)
} vertexAttributes = vertexAttributes or attr.flag
fun shadowQuad(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder {
shadowLine(x0, y0, x1, y0)
shadowLine(x1, y0, x1, y1)
shadowLine(x1, y1, x0, y1)
shadowLine(x0, y1, x0, y0)
return this
}
// Помощники
fun quad(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): VertexBuilder {
check(mode?.elements == 4) { "Currently building $mode" }
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 return this
} }
fun quadRotated( private fun pushFloat4(attr: VertexAttributes.Attribute, x: Float, y: Float, z: Float, w: Float): VertexBuilder {
x0: Float, vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
y0: Float, vertexMemory.putFloat(x)
x1: Float, vertexMemory.putFloat(y)
y1: Float, vertexMemory.putFloat(z)
x: Float, vertexMemory.putFloat(w)
y: Float, vertexAttributes = vertexAttributes or attr.flag
angle: Double,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): VertexBuilder {
check(mode?.elements == 4) { "Currently building $mode" }
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 return this
} }
fun quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): VertexBuilder { fun position(x: Float, y: Float): VertexBuilder {
return quad( return pushFloat2(requireNotNull(attributes[VertexAttributeType.POSITION]) { "Vertex format does not have position attribute" }, x, y)
aabb.mins.x.toFloat(),
aabb.mins.y.toFloat(),
aabb.maxs.x.toFloat(),
aabb.maxs.y.toFloat(),
lambda
)
} }
fun quadZ( fun position(value: IStruct2f) = position(value.component1(), value.component2())
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): VertexBuilder {
check(mode?.elements == 4) { "Currently building $mode" }
lambda(vertex().pushVec3f(x0, y0, z), 0).end() fun uv(x: Float, y: Float): VertexBuilder {
lambda(vertex().pushVec3f(x1, y0, z), 1).end() return pushFloat2(requireNotNull(attributes[VertexAttributeType.UV]) { "Vertex format does not have texture UV attribute" }, x, y)
lambda(vertex().pushVec3f(x0, y1, z), 2).end()
lambda(vertex().pushVec3f(x1, y1, z), 3).end()
return this
} }
fun quadRotatedZ( fun uv(value: IStruct2f) = position(value.component1(), value.component2())
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
x: Float,
y: Float,
angle: Double,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): VertexBuilder {
check(mode?.elements == 4) { "Currently building $mode" }
val s = sin(angle).toFloat() fun color(x: Float, y: Float, z: Float, w: Float): VertexBuilder {
val c = cos(angle).toFloat() return pushFloat4(requireNotNull(attributes[VertexAttributeType.COLOR]) { "Vertex format does not have color attribute" }, x, y, z, w)
}
lambda(vertex().pushVec3f(x + x0 * c - s * y0, y + s * x0 + c * y0, z), 0).end() fun color(value: IStruct4f) = color(value.component1(), value.component2(), value.component3(), value.component4())
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 fun hueShift(x: Float): VertexBuilder {
return pushFloat(requireNotNull(attributes[VertexAttributeType.HUE_SHIFT]) { "Vertex format does not have texture UV attribute" }, x)
} }
} }

View File

@ -5,6 +5,10 @@ import ru.dbotthepony.kbox2d.api.IDebugDraw
import ru.dbotthepony.kbox2d.api.Transform import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import kotlin.math.cos import kotlin.math.cos
@ -12,6 +16,8 @@ import kotlin.math.sin
class Box2DRenderer : IDebugDraw { class Box2DRenderer : IDebugDraw {
val state = StarboundClient.current() val state = StarboundClient.current()
private val identity = Matrix3f.identity()
override var drawShapes: Boolean = false override var drawShapes: Boolean = false
override var drawJoints: Boolean = false override var drawJoints: Boolean = false
override var drawAABB: Boolean = false override var drawAABB: Boolean = false
@ -19,25 +25,26 @@ class Box2DRenderer : IDebugDraw {
override var drawPairs: Boolean = false override var drawPairs: Boolean = false
override var drawCenterOfMess: Boolean = false override var drawCenterOfMess: Boolean = false
private val builder = StreamVertexBuilder(VertexAttributes.POSITION)
override fun drawPolygon(vertices: List<Vector2d>, color: RGBAColor) { override fun drawPolygon(vertices: List<Vector2d>, color: RGBAColor) {
require(vertices.size > 1) { "Vertex list had only ${vertices.size} namings in it" } require(vertices.size > 1) { "Vertex list had only ${vertices.size} namings in it" }
val builder = state.flat2DLines
builder.builder.begin() builder.builder.begin()
for (i in vertices.indices) { for (i in vertices.indices) {
val current = vertices[i] val current = vertices[i]
val next = vertices[(i + 1) % vertices.size] val next = vertices[(i + 1) % vertices.size]
builder.builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat()) builder.builder.vertex(current.x.toFloat(), current.y.toFloat())
builder.builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat()) builder.builder.vertex(next.x.toFloat(), next.y.toFloat())
} }
builder.upload() builder.upload()
state.programs.flat.use() state.programs.position.use()
state.programs.flat.color = color state.programs.position.colorMultiplier = color
state.programs.flat.transform = state.matrixStack.last() state.programs.position.worldMatrix = state.stack.last()
state.programs.position.modelMatrix = identity
builder.draw(GL_LINES) builder.draw(GL_LINES)
} }
@ -45,25 +52,24 @@ class Box2DRenderer : IDebugDraw {
private fun drawSolid(vertices: List<Vector2d>, color: RGBAColor) { private fun drawSolid(vertices: List<Vector2d>, color: RGBAColor) {
require(vertices.size >= 3) { "Vertex list had only ${vertices.size} namings in it" } require(vertices.size >= 3) { "Vertex list had only ${vertices.size} namings in it" }
val builder = state.flat2DTriangles builder.builder.begin(GeometryType.TRIANGLES)
builder.builder.begin()
val zero = vertices[0] val zero = vertices[0]
for (i in 1 until vertices.size) { for (i in 1 until vertices.size) {
val current = vertices[i] val current = vertices[i]
val next = vertices[(i + 1) % vertices.size] val next = vertices[(i + 1) % vertices.size]
builder.builder.vertex().pushVec2f(zero.x.toFloat(), zero.y.toFloat()) builder.builder.vertex(zero.x.toFloat(), zero.y.toFloat())
builder.builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat()) builder.builder.vertex(current.x.toFloat(), current.y.toFloat())
builder.builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat()) builder.builder.vertex(next.x.toFloat(), next.y.toFloat())
} }
builder.upload() builder.upload()
state.programs.flat.use() state.programs.position.use()
state.programs.flat.color = color state.programs.position.colorMultiplier = color
state.programs.flat.transform = state.matrixStack.last() state.programs.position.worldMatrix = state.stack.last()
state.programs.position.modelMatrix = identity
builder.draw(GL_TRIANGLES) builder.draw(GL_TRIANGLES)
} }

View File

@ -7,11 +7,10 @@ import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.freetype.LoadFlag import ru.dbotthepony.kstarbound.client.freetype.LoadFlag
import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode import ru.dbotthepony.kstarbound.client.freetype.struct.FT_Pixel_Mode
import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
private fun breakLines(text: String): List<String> { private fun breakLines(text: String): List<String> {
@ -91,7 +90,6 @@ class Font(
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
scale: Float = 1f, scale: Float = 1f,
stack: Matrix4fStack = state.matrixStack,
): TextSize { ): TextSize {
if (text.isEmpty()) if (text.isEmpty())
return TextSize(0f, 0f) return TextSize(0f, 0f)
@ -102,20 +100,22 @@ class Font(
val totalX = totalSize.width val totalX = totalSize.width
val totalY = totalSize.height val totalY = totalSize.height
stack.push() val model = Matrix3f.identity()
when (alignY) { when (alignY) {
TextAlignY.TOP -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale + y) TextAlignY.TOP -> model.translate(x = x, y = lineHeight * scale + y)
TextAlignY.CENTER -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale - totalY * scale / 2f + y) TextAlignY.CENTER -> model.translate(x = x, y = lineHeight * scale - totalY * scale / 2f + y)
TextAlignY.BOTTOM -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale - totalY * scale + y) TextAlignY.BOTTOM -> model.translate(x = x, y = lineHeight * scale - totalY * scale + y)
} }
if (scale != 1f) if (scale != 1f)
stack.last().scale(x = scale, y = scale) model.scale(x = scale, y = scale)
state.programs.font.use() state.programs.font.use()
state.programs.font.color = color state.programs.font.colorMultiplier = color
state.programs.font.worldMatrix = state.stack.last()
state.activeTexture = 0 state.activeTexture = 0
state.programs.font.texture = 0
val space = getGlyph(' ') val space = getGlyph(' ')
@ -123,7 +123,7 @@ class Font(
for (line in text) { for (line in text) {
if (line == "") { if (line == "") {
stack.last().translateWithMultiplication(y = lineHeight) model.translate(y = lineHeight)
continue continue
} }
@ -134,12 +134,12 @@ class Font(
TextAlignX.CENTER -> { TextAlignX.CENTER -> {
movedX = totalX / 2f - lineWidth(line, space) / 2f movedX = totalX / 2f - lineWidth(line, space) / 2f
stack.last().translateWithMultiplication(x = movedX) model.translate(x = movedX)
} }
TextAlignX.RIGHT -> { TextAlignX.RIGHT -> {
movedX = -lineWidth(line, space) movedX = -lineWidth(line, space)
stack.last().translateWithMultiplication(x = movedX) model.translate(x = movedX)
} }
} }
@ -150,10 +150,10 @@ class Font(
if (chr == '\t') { if (chr == '\t') {
if (lineGlyphs % 4 == 0) { if (lineGlyphs % 4 == 0) {
advancedX += space.advanceX * 4 advancedX += space.advanceX * 4
stack.last().translateWithMultiplication(x = space.advanceX * 4) model.translate(x = space.advanceX * 4)
} else { } else {
advancedX += space.advanceX * (lineGlyphs % 4) advancedX += space.advanceX * (lineGlyphs % 4)
stack.last().translateWithMultiplication(x = space.advanceX * (lineGlyphs % 4)) model.translate(x = space.advanceX * (lineGlyphs % 4))
} }
lineGlyphs += lineGlyphs % 4 lineGlyphs += lineGlyphs % 4
@ -161,17 +161,16 @@ class Font(
} }
val glyph = getGlyph(chr) val glyph = getGlyph(chr)
glyph.render(stack) glyph.render(model)
lineWidth += glyph.advanceX lineWidth += glyph.advanceX
lineGlyphs++ lineGlyphs++
} }
advancedX = advancedX.coerceAtLeast(lineWidth) advancedX = advancedX.coerceAtLeast(lineWidth)
stack.last().translateWithMultiplication(x = -lineWidth - movedX, y = lineHeight) model.translate(x = -lineWidth - movedX, y = lineHeight)
} }
state.vao = null state.vao = null
stack.pop()
return TextSize(totalX * scale, totalY * scale) return TextSize(totalX * scale, totalY * scale)
} }
@ -188,7 +187,6 @@ class Font(
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
scale: Float = 1f, scale: Float = 1f,
stack: Matrix4fStack = state.matrixStack,
): TextSize { ): TextSize {
return render( return render(
breakLines(text), breakLines(text),
@ -197,7 +195,6 @@ class Font(
alignX = alignX, alignX = alignX,
alignY = alignY, alignY = alignY,
scale = scale, scale = scale,
stack = stack,
color = color, color = color,
) )
} }
@ -210,10 +207,10 @@ class Font(
if (chr == '\t') { if (chr == '\t') {
if (lineGlyphs % 4 == 0) { if (lineGlyphs % 4 == 0) {
lineWidth += space.advanceX * 4 lineWidth += space.advanceX * 4
state.matrixStack.last().translateWithMultiplication(x = space.advanceX * 4) state.stack.last().translate(x = space.advanceX * 4)
} else { } else {
lineWidth += space.advanceX * (lineGlyphs % 4) lineWidth += space.advanceX * (lineGlyphs % 4)
state.matrixStack.last().translateWithMultiplication(x = space.advanceX * (lineGlyphs % 4)) state.stack.last().translate(x = space.advanceX * (lineGlyphs % 4))
} }
lineGlyphs += lineGlyphs % 4 lineGlyphs += lineGlyphs % 4
@ -258,9 +255,7 @@ class Font(
val advanceX: Float val advanceX: Float
val advanceY: Float val advanceY: Float
private val vbo: BufferObject.VBO? // все три указателя должны хранится во избежание утечки private val vao: VertexArrayObject?
private val ebo: BufferObject.EBO? // все три указателя должны хранится во избежание утечки
private val vao: VertexArrayObject? // все три указателя должны хранится во избежание утечки
private val indexCount: Int private val indexCount: Int
@ -301,54 +296,48 @@ class Font(
glPixelStorei(GL_UNPACK_ALIGNMENT, 4) glPixelStorei(GL_UNPACK_ALIGNMENT, 4)
vao = state.newVAO() vao = state.newVAO()
ebo = state.newEBO() val ebo = state.newEBO()
vbo = state.newVBO() val vbo = state.newVBO()
vao.bind() vao.elementBuffer = ebo
ebo.bind() vao.bindAttributes(vbo, VertexAttributes.POSITION_UV)
vbo.bind()
val builder = VertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, GeometryType.QUADS) val builder = VertexBuilder(VertexAttributes.POSITION_UV, GeometryType.QUADS)
builder.vertex(0f, 0f).uv(0f, 0f)
builder.vertex(width, 0f).uv(1f, 0f)
builder.vertex(width, height).uv(1f, 1f)
builder.vertex(0f, height).uv(0f, 1f)
builder.quad(0f, 0f, width, height, QuadTransformers.uv())
builder.upload(vbo, ebo, GL_STATIC_DRAW) builder.upload(vbo, ebo, GL_STATIC_DRAW)
builder.attributes.apply(vao, true)
indexCount = builder.indexCount indexCount = builder.indexCount
elementIndexType = builder.indexType elementIndexType = builder.indexType
vao.unbind()
ebo.unbind()
vbo.unbind()
} else { } else {
isEmpty = true isEmpty = true
indexCount = 0 indexCount = 0
elementIndexType = 0 elementIndexType = 0
vao = null vao = null
vbo = null
ebo = null
texture = null texture = null
} }
} }
fun render(stack: Matrix4fStack) { fun render(model: Matrix3f) {
if (isEmpty) { if (isEmpty) {
stack.last().translateWithMultiplication(advanceX) model.translate(advanceX)
return return
} }
vao!!.bind() vao!!.bind()
stack.last().translateWithMultiplication(bearingX, -bearingY) model.translate(bearingX, -bearingY)
texture!!.bind() texture!!.bind()
state.programs.font.transform = stack.last() state.programs.font.modelMatrix = model
glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L) glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L)
checkForGLError() checkForGLError()
stack.last().translateWithMultiplication(advanceX - bearingX, bearingY) model.translate(advanceX - bearingX, bearingY)
} }
} }
} }

View File

@ -2,32 +2,27 @@ package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import ru.dbotthepony.kvector.arrays.Matrix3fStack
import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.arrays.Matrix4fStack
import java.util.function.Function
/** /**
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности * Позволяет вызывать отрисовщики в определённой (послойной) последовательности
*/ */
class LayeredRenderer { class LayeredRenderer {
private val layers = Long2ObjectAVLTreeMap<ArrayList<(Matrix4fStack) -> Unit>>() private val layers = Long2ObjectAVLTreeMap<ArrayList<() -> Unit>>()
private val layersHash = Long2ObjectOpenHashMap<ArrayList<(Matrix4fStack) -> Unit>>()
fun add(layer: Long, renderer: (Matrix4fStack) -> Unit) { fun add(layer: Long, renderer: () -> Unit) {
var list = layersHash[layer] layers.computeIfAbsent(layer, Function { ArrayList() }).add(renderer)
if (list == null) {
list = ArrayList()
layers[layer] = list
layersHash[layer] = list
}
list.add(renderer)
} }
fun render(stack: Matrix4fStack) { fun render() {
for (list in layers.values) { for (list in layers.values) {
for (renderer in list) { for (renderer in list) {
renderer.invoke(stack) renderer.invoke()
} }
} }
layers.clear()
} }
} }

View File

@ -18,25 +18,22 @@ class Mesh() {
val vbo = state.newVBO() val vbo = state.newVBO()
val ebo = state.newEBO() val ebo = state.newEBO()
init {
vao.elementBuffer = ebo
}
var indexCount = 0 var indexCount = 0
private set private set
var indexType = 0 var indexType = 0
private set private set
fun load(builder: VertexBuilder, mode: Int = GL45.GL_DYNAMIC_DRAW) { fun load(builder: VertexBuilder, mode: Int = GL45.GL_DYNAMIC_DRAW) {
vao.bind()
vbo.bind()
ebo.bind()
builder.upload(vbo, ebo, mode) builder.upload(vbo, ebo, mode)
builder.attributes.apply(vao, true)
vao.bindAttributes(vbo, builder.attributes)
indexCount = builder.indexCount indexCount = builder.indexCount
indexType = builder.indexType indexType = builder.indexType
vao.unbind()
vbo.unbind()
ebo.unbind()
} }
fun render() { fun render() {
@ -48,8 +45,8 @@ class Mesh() {
} }
data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh()) { data class ConfiguredMesh<T : GLShaderProgram>(val config: RenderConfig<T>, val mesh: Mesh = Mesh()) {
fun render(transform: Matrix4f = config.client.matrixStack.last()) { fun render() {
config.setup(transform) config.setup()
mesh.render() mesh.render()
config.uninstall() config.uninstall()
} }

View File

@ -7,7 +7,7 @@ abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
val client get() = program.client val client get() = program.client
open val initialBuilderCapacity: Int get() = 64 open val initialBuilderCapacity: Int get() = 64
open fun setup(transform: Matrix4f = client.matrixStack.last()) { open fun setup() {
program.use() program.use()
} }

View File

@ -6,13 +6,13 @@ import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.gl.shader.GLTileProgram import ru.dbotthepony.kstarbound.client.gl.shader.UberShader
import ru.dbotthepony.kstarbound.client.gl.vertex.* import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.* import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import kotlin.collections.HashMap import kotlin.collections.HashMap
@ -27,6 +27,7 @@ class TileRenderers(val client: StarboundClient) {
private val background = HashMap<GLTexture2D, Config>() private val background = HashMap<GLTexture2D, Config>()
private val matCache = HashMap<String, TileRenderer>() private val matCache = HashMap<String, TileRenderer>()
private val modCache = HashMap<String, TileRenderer>() private val modCache = HashMap<String, TileRenderer>()
private val identity = Matrix3f.identity()
fun getMaterialRenderer(defName: String): TileRenderer { fun getMaterialRenderer(defName: String): TileRenderer {
return matCache.computeIfAbsent(defName) { return matCache.computeIfAbsent(defName) {
@ -42,31 +43,32 @@ class TileRenderers(val client: StarboundClient) {
} }
} }
private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(client.programs.tile) { private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<UberShader>(client.programs.tile) {
override val initialBuilderCapacity: Int override val initialBuilderCapacity: Int
get() = 1024 get() = 1024
override fun setup(transform: Matrix4f) { override fun setup() {
super.setup(transform) super.setup()
client.activeTexture = 0 client.activeTexture = 0
client.depthTest = false client.depthTest = false
program.texture = 0 program.texture0 = 0
texture.bind() texture.bind()
texture.textureMagFilter = GL_NEAREST texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST texture.textureMinFilter = GL_NEAREST
program.transform = transform program.worldMatrix = client.stack.last()
program.color = color program.modelMatrix = identity
program.colorMultiplier = color
} }
override fun uninstall() {} override fun uninstall() {}
} }
fun foreground(texture: GLTexture2D): RenderConfig<GLTileProgram> { fun foreground(texture: GLTexture2D): RenderConfig<UberShader> {
return foreground.computeIfAbsent(texture) { Config(it, FOREGROUND_COLOR) } return foreground.computeIfAbsent(texture) { Config(it, FOREGROUND_COLOR) }
} }
fun background(texture: GLTexture2D): RenderConfig<GLTileProgram> { fun background(texture: GLTexture2D): RenderConfig<UberShader> {
return background.computeIfAbsent(texture) { Config(it, BACKGROUND_COLOR) } return background.computeIfAbsent(texture) { Config(it, BACKGROUND_COLOR) }
} }
@ -146,7 +148,13 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
val (u0, v0) = texture!!.pixelToUV(mins) val (u0, v0) = texture!!.pixelToUV(mins)
val (u1, v1) = texture.pixelToUV(maxs) val (u1, v1) = texture.pixelToUV(maxs)
builder.quadZ(a, b, c, d, Z_LEVEL, QuadTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (isModifier) self.modifierHueShift else self.hueShift) }) val hue = if (isModifier) self.modifierHueShift else self.hueShift
// flip uv since in-world coordinates are flipped relative to screen
builder.vertex(a, b).uv(u0, v1).hueShift(hue)
builder.vertex(c, b).uv(u1, v1).hueShift(hue)
builder.vertex(c, d).uv(u1, v0).hueShift(hue)
builder.vertex(a, d).uv(u0, v0).hueShift(hue)
} }
private fun tesselatePiece( private fun tesselatePiece(
@ -222,7 +230,6 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
} }
companion object { companion object {
const val Z_LEVEL = 10f
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
} }
} }

View File

@ -9,9 +9,6 @@ class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChu
private val def = entity.def private val def = entity.def
override fun render(stack: Matrix4fStack) { override fun render(stack: Matrix4fStack) {
client.programs.textured.use()
client.programs.textured.transform = stack.last()
client.activeTexture = 0
client.programs.textured.texture = 0
} }
} }

View File

@ -142,7 +142,10 @@ class ClientWorld(
val state = view.getCell(x, y) val state = view.getCell(x, y)
if (state?.liquid?.def === type) { if (state?.liquid?.def === type) {
builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level) builder.vertex(x.toFloat(), y.toFloat())
builder.vertex(x.toFloat() + 1f, y.toFloat())
builder.vertex(x.toFloat() + 1f, y.toFloat() + 1f)
builder.vertex(x.toFloat(), y.toFloat() + 1f)
} }
} }
} }
@ -153,37 +156,37 @@ class ClientWorld(
for ((baked, zLevel) in background.bakedMeshes) { for ((baked, zLevel) in background.bakedMeshes) {
layers.add(zLevel + RenderLayer.BackgroundTile.index) { layers.add(zLevel + RenderLayer.BackgroundTile.index) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
baked.render(it.last()) baked.render()
it.pop() client.stack.pop()
} }
} }
for ((baked, zLevel) in foreground.bakedMeshes) { for ((baked, zLevel) in foreground.bakedMeshes) {
layers.add(zLevel + RenderLayer.ForegroundTile.index) { layers.add(zLevel + RenderLayer.ForegroundTile.index) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
baked.render(it.last()) baked.render()
it.pop() client.stack.pop()
} }
} }
if (liquidMesh.isNotEmpty()) { /*if (liquidMesh.isNotEmpty()) {
layers.add(RenderLayer.Liquid.index) { layers.add(RenderLayer.Liquid.index) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
val program = client.programs.liquid val program = client.programs.liquid
program.use() program.use()
program.transform = it.last() program.transform = client.stack.last()
for ((mesh, color) in liquidMesh) { for ((mesh, color) in liquidMesh) {
program.baselineColor = color program.baselineColor = color
mesh.render() mesh.render()
} }
it.pop() client.stack.pop()
} }
} }*/
} }
} }
@ -266,24 +269,21 @@ class ClientWorld(
for (obj in objects) { for (obj in objects) {
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) { if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
//layers.add(RenderLayer.Object.index) { //layers.add(RenderLayer.Object.index) {
layers.add(obj.orientation?.renderLayer ?: continue) { m -> layers.add(obj.orientation?.renderLayer ?: continue) {
client.quadWireframe { client.quadWireframe {
it.quad( it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat())
obj.pos.x.toFloat(), it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat())
obj.pos.y.toFloat(), it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat() + 1f)
obj.pos.x + 1f, it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat() + 1f)
obj.pos.y + 1f,
)
} }
obj.drawables.forEach { obj.drawables.forEach {
val (x, y) = obj.imagePosition val (x, y) = obj.imagePosition
it.render( client.stack.push().last()
client, .translate(obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf)
x = obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, it.render(client)
y = obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf client.stack.pop()
)
} }
} }

View File

@ -10,18 +10,23 @@ import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.io.json.consumeNull
import ru.dbotthepony.kstarbound.math.LineF import ru.dbotthepony.kstarbound.math.LineF
import ru.dbotthepony.kstarbound.util.Either
import ru.dbotthepony.kstarbound.util.contains import ru.dbotthepony.kstarbound.util.contains
import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector3f import ru.dbotthepony.kvector.vector.Vector3f
import kotlin.math.sin
sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) { sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) {
@JsonFactory
data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either<Float, Vector2f> = Either.left(1f))
class Line( class Line(
val line: LineF, val line: LineF,
val width: Float, val width: Float,
@ -29,7 +34,11 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
fullbright: Boolean = false fullbright: Boolean = false
) : Drawable(position, color, fullbright) { ) : Drawable(position, color, fullbright) {
override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { override fun render(client: StarboundClient, x: Float, y: Float) {
TODO("Not yet implemented")
}
override fun flop(): Drawable {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }
@ -40,15 +49,18 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
fullbright: Boolean = false fullbright: Boolean = false
) : Drawable(position, color, fullbright) { ) : Drawable(position, color, fullbright) {
override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { override fun render(client: StarboundClient, x: Float, y: Float) {
TODO("Not yet implemented")
}
override fun flop(): Drawable {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }
class Image( class Image(
val path: SpriteReference, val path: SpriteReference,
val transform: Matrix3f, val transform: Either<Matrix3f, Transformations>,
val centered: Boolean,
position: Vector2f = Vector2f.ZERO, position: Vector2f = Vector2f.ZERO,
color: RGBAColor = RGBAColor.WHITE, color: RGBAColor = RGBAColor.WHITE,
fullbright: Boolean = false fullbright: Boolean = false
@ -60,34 +72,73 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
return this return this
} }
return Image(newPath, transform, centered, position, color, fullbright) return Image(newPath, transform, position, color, fullbright)
} }
override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { override fun flop(): Drawable {
return Image(path, transform.flatMap({ it.copy().scale(-1f, 1f) }, { it.copy(mirrored = !it.mirrored) }), position, color, fullbright)
}
override fun render(client: StarboundClient, x: Float, y: Float) {
val sprite = path.sprite ?: return val sprite = path.sprite ?: return
val texture = client.loadTexture(path.imagePath.value!!) val texture = client.loadTexture(path.imagePath.value!!)
val program = client.programs.positionTexture
if (centered) { program.modelMatrix = transform.map({ it }, {
client.quadTexture(texture) { val mat = Matrix3f.identity()
it.quad(x - (sprite.width / PIXELS_IN_STARBOUND_UNITf) * 0.5f, y - (sprite.height / PIXELS_IN_STARBOUND_UNITf) * 0.5f, x + sprite.width / PIXELS_IN_STARBOUND_UNITf * 0.5f, y + sprite.height / PIXELS_IN_STARBOUND_UNITf * 0.5f, QuadTransformers.uv(sprite))
it.scale.map({ mat.scale(it / PIXELS_IN_STARBOUND_UNITf, it / PIXELS_IN_STARBOUND_UNITf) }, { mat.scale(it / PIXELS_IN_STARBOUND_UNITf) })
if (it.centered) {
mat.translate(sprite.width / -2f, sprite.height / -2f)
} }
} else {
client.quadTexture(texture) { if (it.rotation != 0f) {
it.quad(x, y, x + sprite.width / PIXELS_IN_STARBOUND_UNITf, y + sprite.height / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite)) mat.rotateAroundZ(it.rotation)
} }
}
if (it.mirrored) {
mat.translate(sprite.width.toFloat(), 0f)
mat.scale(-1f, 1f)
}
mat
})
program.worldMatrix = client.stack.last()
program.use()
program.texture0 = 0
client.activeTexture = 0
texture.bind()
program.builder.builder.begin(GeometryType.QUADS)
program.builder.builder.vertex(x, y).uv(sprite.u0, sprite.v0)
program.builder.builder.vertex(x + sprite.width, y).uv(sprite.u1, sprite.v0)
program.builder.builder.vertex(x + sprite.width, y + sprite.height).uv(sprite.u1, sprite.v1)
program.builder.builder.vertex(x, y + sprite.height).uv(sprite.u0, sprite.v1)
program.builder.upload()
program.builder.draw()
} }
} }
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) { class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {} override fun render(client: StarboundClient, x: Float, y: Float) {}
override fun flop(): Drawable {
return this
}
} }
open fun with(values: (String) -> String?): Drawable { open fun with(values: (String) -> String?): Drawable {
return this return this
} }
abstract fun render(client: StarboundClient = StarboundClient.current(), stack: Matrix4fStack = client.matrixStack, x: Float = 0f, y: Float = 0f) abstract fun render(client: StarboundClient = StarboundClient.current(), x: Float = 0f, y: Float = 0f)
/**
* mirror along X axis
*/
abstract fun flop(): Drawable
companion object { companion object {
val EMPTY = Empty() val EMPTY = Empty()
@ -102,6 +153,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
private val colors = gson.getAdapter(RGBAColor::class.java) private val colors = gson.getAdapter(RGBAColor::class.java)
private val images = gson.getAdapter(SpriteReference::class.java) private val images = gson.getAdapter(SpriteReference::class.java)
private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter<ImmutableList<Vector2f>> private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter<ImmutableList<Vector2f>>
private val transformations = gson.getAdapter(Transformations::class.java)
override fun write(out: JsonWriter?, value: Drawable?) { override fun write(out: JsonWriter?, value: Drawable?) {
TODO("Not yet implemented") TODO("Not yet implemented")
@ -123,9 +175,9 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
return Poly(vertices.fromJsonTree(value["poly"]), position, color, fullbright) return Poly(vertices.fromJsonTree(value["poly"]), position, color, fullbright)
} else if ("image" in value) { } else if ("image" in value) {
val image = images.fromJsonTree(value["image"]) val image = images.fromJsonTree(value["image"])
val mat = Matrix3f.identity()
if ("transformation" in value) { if ("transformation" in value) {
val mat = Matrix3f.identity()
val array = value["transformation"].asJsonArray val array = value["transformation"].asJsonArray
// original starbound use GLM, which reflects OpenGL, which in turn make matrices row-major // original starbound use GLM, which reflects OpenGL, which in turn make matrices row-major
@ -144,26 +196,25 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
mat.r20 = row2.x mat.r20 = row2.x
mat.r21 = row2.y mat.r21 = row2.y
mat.r22 = row2.z mat.r22 = row2.z
return Image(
image,
Either.left(mat),
position,
color,
fullbright
)
} else { } else {
if ("rotation" in value) { return Image(
LOGGER.warn("Rotation is not supported yet (required by ${image.raw})") image,
} Either.right(transformations.fromJsonTree(value)),
position,
if ("mirrored" in value && value["mirrored"].asBoolean) { color,
mat.scale(-1f, -1f) fullbright
} )
if ("scale" in value) {
if (value["scale"].isJsonArray) {
mat.scale(vectors.fromJsonTree(value["scale"]))
} else {
val scale = value["scale"].asFloat
mat.scale(scale, scale)
}
}
} }
return Image(image, mat, value["centered"]?.asBoolean ?: false, position, color, fullbright)
} else { } else {
return Empty(position, color, fullbright) return Empty(position, color, fullbright)
} }

View File

@ -130,10 +130,22 @@ data class ObjectOrientation(
if ("imageLayers" in obj) { if ("imageLayers" in obj) {
for (value in obj["imageLayers"].asJsonArray) { for (value in obj["imageLayers"].asJsonArray) {
drawables.add(this.drawables.fromJsonTree(value)) var result = this.drawables.fromJsonTree(value)
if (flipImages) {
result = result.flop()
}
drawables.add(result)
} }
} else { } else {
drawables.add(this.drawables.fromJsonTree(obj)) var result = this.drawables.fromJsonTree(obj)
if (flipImages) {
result = result.flop()
}
drawables.add(result)
} }
val imagePosition = (obj["imagePosition"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO) / PIXELS_IN_STARBOUND_UNITf val imagePosition = (obj["imagePosition"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO) / PIXELS_IN_STARBOUND_UNITf

View File

@ -7,59 +7,35 @@ import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
* *
* JSON адаптер реализуется через [EitherTypeAdapter] * JSON адаптер реализуется через [EitherTypeAdapter]
*/ */
sealed class Either<L, R> { class Either<L, R> private constructor(val left: KOptional<L>, val right: KOptional<R>) {
class Left<L, R>(val value: L) : Either<L, R>() { val isLeft: Boolean get() = left.isPresent
override val isLeft: Boolean val isRight: Boolean get() = right.isPresent
get() = true
override val isRight: Boolean
get() = false
override fun <T> map(left: (L) -> T, right: (R) -> T): T { inline fun <T> map(left: (L) -> T, right: (R) -> T): T {
return left.invoke(this.value) this.left.ifPresent {
return left.invoke(it)
} }
override fun left(): L { return right.invoke(this.right.value)
return value
}
override fun right(): R {
throw NoSuchElementException()
}
} }
class Right<L, R>(val value: R) : Either<L, R>() { inline fun <NL, NR> flatMap(left: (L) -> NL, right: (R) -> NR): Either<NL, NR> {
override val isLeft: Boolean this.left.ifPresent {
get() = false return left(left.invoke(it))
override val isRight: Boolean
get() = true
override fun <T> map(left: (L) -> T, right: (R) -> T): T {
return right.invoke(value)
} }
override fun left(): L { return right(right.invoke(this.right.value))
throw NoSuchElementException()
}
override fun right(): R {
return value
}
} }
abstract val isLeft: Boolean
abstract val isRight: Boolean
abstract fun <T> map(left: (L) -> T, right: (R) -> T): T
/** /**
* @throws NoSuchElementException * @throws NoSuchElementException
*/ */
abstract fun left(): L fun left(): L = left.value
/** /**
* @throws NoSuchElementException * @throws NoSuchElementException
*/ */
abstract fun right(): R fun right(): R = right.value
inline fun leftOrElse(orElse: () -> L): L { inline fun leftOrElse(orElse: () -> L): L {
if (isLeft) if (isLeft)
@ -77,11 +53,11 @@ sealed class Either<L, R> {
companion object { companion object {
fun <L, R> left(value: L): Either<L, R> { fun <L, R> left(value: L): Either<L, R> {
return Left(value) return Either(KOptional.of(value), KOptional.empty())
} }
fun <L, R> right(value: R): Either<L, R> { fun <L, R> right(value: R): Either<L, R> {
return Right(value) return Either(KOptional.empty(), KOptional.of(value))
} }
} }
} }

View File

@ -8,6 +8,7 @@ import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
import java.lang.ref.Reference
operator fun JsonObject.set(key: String, value: JsonElement?) { add(key, value) } operator fun JsonObject.set(key: String, value: JsonElement?) { add(key, value) }
operator fun JsonObject.set(key: String, value: String) { add(key, JsonPrimitive(value)) } operator fun JsonObject.set(key: String, value: String) { add(key, JsonPrimitive(value)) }
@ -147,3 +148,17 @@ inline fun <reified T : JsonElement> JsonObject.get(key: String, orElse: () -> T
return value return value
} }
inline fun <T> MutableIterable<Reference<T>>.forEachValid(block: (T) -> Unit) {
val i = iterator()
for (v in i) {
val get = v.get()
if (get == null) {
i.remove()
} else {
block.invoke(get)
}
}
}

View File

@ -9,7 +9,7 @@ import java.lang.ref.WeakReference
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
// hand-rolled interner, which has similar performance to ConcurrentHashMap // hand-rolled interner, which has similar performance to ConcurrentHashMap
// (given there is no strong congestion, in which case it performs somewhere above Caffeine interner), // (given there is no strong congestion, otherwise it performs somewhere above Caffeine interner),
// while yielding significantly better memory utilization than both // while yielding significantly better memory utilization than both
class HashTableInterner<T : Any>(private val segmentBits: Int) : Interner<T> { class HashTableInterner<T : Any>(private val segmentBits: Int) : Interner<T> {
companion object { companion object {

View File

@ -10,6 +10,9 @@ fun <T> KOptional(value: T) = KOptional.of(value)
* in more elegant solution than handling nullable Optionals * in more elegant solution than handling nullable Optionals
*/ */
class KOptional<T> private constructor(private val _value: T, val isPresent: Boolean) { class KOptional<T> private constructor(private val _value: T, val isPresent: Boolean) {
/**
* @throws NoSuchElementException
*/
val value: T get() { val value: T get() {
if (isPresent) { if (isPresent) {
return _value return _value

View File

@ -1,9 +0,0 @@
#version 330
uniform vec4 color;
out vec4 color_out;
void main() {
color_out = color;
}

View File

@ -1,9 +0,0 @@
#version 330
layout (location = 0) in vec2 pos;
uniform mat4 transform;
void main() {
gl_Position = transform * vec4(pos, 0.5, 1.0);
}

View File

@ -0,0 +1,59 @@
uniform vec4 colorMultiplier;
out vec4 colorResult;
#ifdef TEXTURE
uniform sampler2D texture0;
in vec2 uvOut;
#endif
#ifdef COLOR
in vec4 vertexColor;
#endif
#ifdef HUE_SHIFT
in float hueShiftOut;
#endif
// https://gist.github.com/983/e170a24ae8eba2cd174f
// http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
// https://stackoverflow.com/questions/15095909/from-rgb-to-hsv-in-opengl-glsl
// All components are in the range [0…1], including hue.
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
// All components are in the range [0…1], including hue.
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
colorResult = vec4(1, 1, 1, 1);
#ifdef TEXTURE
colorResult *= texture(texture0, uvOut);
#endif
#ifdef HUE_SHIFT
vec3 hsv2 = rgb2hsv(colorResult.rgb);
hsv2[0] += hueShiftOut / 360;
colorResult = vec4(hsv2rgb(hsv2), colorResult.a);
#endif
#ifdef COLOR
colorResult *= vertexColor;
#endif
colorResult *= colorMultiplier;
}

View File

@ -0,0 +1,13 @@
#version 330
uniform sampler2D texture0;
uniform vec4 colorMultiplier;
out vec4 colorResult;
in vec2 uvOut;
void main() {
colorResult = colorMultiplier * texture(texture0, uvOut).r;
}

View File

@ -6,9 +6,10 @@ layout (location = 1) in vec2 uvCoords;
out vec2 oUVCoords; out vec2 oUVCoords;
uniform mat4 transform; uniform mat3 transform;
void main() { void main() {
gl_Position = transform * vec4(vertexPos, 0.0, 1.0); vec3 result = transform * vec3(vertexPos, 1.0);
gl_Position = vec4(result.x, result.y, 0.0, result.z);
oUVCoords = uvCoords; oUVCoords = uvCoords;
} }

View File

@ -1,38 +0,0 @@
#version 330
uniform sampler2D texture0;
uniform vec4 color;
in vec2 uv_out;
in float hsv_vertex;
out vec4 color_out;
// https://gist.github.com/983/e170a24ae8eba2cd174f
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec4 texel = texture(texture0, uv_out);
vec3 hsv2 = rgb2hsv(vec3(texel[0], texel[1], texel[2]));
hsv2[0] += hsv_vertex / 360;
vec4 rgb2 = vec4(hsv2rgb(hsv2), texel[3]);
color_out = color * rgb2;
}

View File

@ -1,16 +0,0 @@
#version 330
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 uv_in;
layout (location = 2) in float hsv_in;
out vec2 uv_out;
out float hsv_vertex;
uniform mat4 transform;
void main() {
uv_out = uv_in;
hsv_vertex = hsv_in;
gl_Position = transform * vec4(pos, 1.0);
}

View File

@ -0,0 +1,38 @@
layout (location = 0) in vec2 pos;
#ifdef TEXTURE
layout (location = TEXTURE) in vec2 uvIn;
out vec2 uvOut;
#endif
#ifdef COLOR
layout (location = COLOR) in vec4 colorIn;
out vec4 vertexColor;
#endif
#ifdef HUE_SHIFT
layout (location = HUE_SHIFT) in float hueShiftIn;
out float hueShiftOut;
#endif
uniform mat3 viewMatrix; // projection (viewport) + camera
uniform mat3 worldMatrix; // matrix stack
uniform mat3 modelMatrix; // local transformations
void main() {
vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0);
gl_Position = vec4(result.x, result.y, 0.0, result.z);
#ifdef HUE_SHIFT
hueShiftOut = hueShiftIn;
#endif
#ifdef COLOR
vertexColor = colorIn;
#endif
#ifdef TEXTURE
uvOut = uvIn;
#endif
}

View File

@ -0,0 +1,18 @@
#version 330
layout (location = 0) in vec2 pos;
layout (location = 1) in vec2 uvData;
uniform mat3 viewMatrix; // projection (viewport)
uniform mat3 worldMatrix; // world position
uniform mat3 modelMatrix; // mesh/local transformations
out vec2 uvOut;
void main() {
vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0);
gl_Position = vec4(result.x, result.y, 0.0, result.z);
uvOut = uvData;
}

View File

@ -3,10 +3,11 @@
layout (location = 0) in vec2 pos; layout (location = 0) in vec2 pos;
uniform mat3 viewMatrix; // camera uniform mat3 viewMatrix; // projection (viewport) + camera
uniform mat3 worldMatrix; // world position uniform mat3 worldMatrix; // matrix stack
uniform mat3 modelMatrix; // mesh/local transformations uniform mat3 modelMatrix; // local transformations
void main() { void main() {
gl_Position = vec4(viewMatrix * worldMatrix * modelMatrix * vec3(pos, 0.0), 1.0); vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0);
gl_Position = vec4(result.x, result.y, 0.0, result.z);
} }

View File

@ -4,14 +4,15 @@
layout (location = 0) in vec2 pos; layout (location = 0) in vec2 pos;
layout (location = 1) in vec4 color; layout (location = 1) in vec4 color;
uniform mat3 viewMatrix; // camera uniform mat3 viewMatrix; // projection (viewport) + camera
uniform mat3 worldMatrix; // world position uniform mat3 worldMatrix; // matrix stack
uniform mat3 modelMatrix; // mesh/local transformations uniform mat3 modelMatrix; // local transformations
out vec4 vertexColor; out vec4 vertexColor;
void main() { void main() {
gl_Position = vec4(viewMatrix * worldMatrix * modelMatrix * vec3(pos, 0.0), 1.0); vec3 result = viewMatrix * worldMatrix * modelMatrix * vec3(pos, 1.0);
gl_Position = vec4(result.x, result.y, 0.0, result.z);
vertexColor = color; vertexColor = color;
} }