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("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")
}

View File

@ -9,6 +9,7 @@ import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL15
import org.lwjgl.opengl.GL45.*
import org.lwjgl.opengl.GLCapabilities
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.GLShader
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.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
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.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.forEachValid
import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix3fStack
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i
import ru.dbotthepony.kvector.vector.Vector3f
import java.io.Closeable
import java.io.File
import java.lang.ref.Cleaner
import java.lang.ref.WeakReference
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.time.Duration
@ -106,19 +107,13 @@ class StarboundClient : Closeable {
var clientTerminated = false
private set
/**
* Матрица преобразования экранных координат (в пикселях) в нормализованные координаты
*/
var viewportMatrixScreen: Matrix4f
var viewportMatrixScreen: Matrix3f
private set
get() = Matrix4f.unmodifiable(field)
get() = Matrix3f.unmodifiable(field)
/**
* Матрица преобразования мировых координат в нормализованные координаты
*/
var viewportMatrixWorld: Matrix4f
var viewportMatrixWorld: Matrix3f
private set
get() = Matrix4f.unmodifiable(field)
get() = Matrix3f.unmodifiable(field)
var isRenderingGame = true
private set
@ -140,12 +135,10 @@ class StarboundClient : Closeable {
thread
}
@Volatile
var objectsCleaned = 0L
var objectsCreated = 0L
private set
@Volatile
var gcHits = 0L
var objectsCleaned = 0L
private set
init {
@ -235,12 +228,19 @@ class StarboundClient : Closeable {
GLFW.glfwShowWindow(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 flat2DTriangles by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.TRIANGLES) }
val flat2DTexturedQuads by lazy { StreamVertexBuilder(GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS) }
val quadWireframe by lazy { StreamVertexBuilder(GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME) }
val maxTextureBlocks = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
val maxVertexAttribBindPoints = glGetInteger(GL_MAX_VERTEX_ATTRIB_BINDINGS)
val stack = Matrix3fStack()
// минимальное время хранения 5 минут и...
private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder()
@ -260,24 +260,26 @@ class StarboundClient : Closeable {
}
private val missingTexturePath = "/assetmissing.png"
private val regularShaderPrograms = ArrayList<WeakReference<GLShaderProgram.Regular>>()
val matrixStack = Matrix4fStack()
val freeType = FreeType()
val font = Font()
val box2dRenderer = Box2DRenderer()
fun addShaderProgram(program: GLShaderProgram) {
if (program is GLShaderProgram.Regular) {
regularShaderPrograms.add(WeakReference(program))
}
}
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
val cleanable = cleaner.register(ref) {
objectsCleaned++
objectsCreated++
val cleanable = cleaner.register(ref) {
if (isSameThread()) {
objectsCleaned++
fn(nativeRef)
checkForGLError()
} else {
gcHits++
synchronized(cleanerBacklog) {
cleanerBacklog.add {
objectsCleaned++
fn(nativeRef)
checkForGLError()
}
@ -318,13 +320,10 @@ class StarboundClient : Closeable {
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 framebuffer by GLObjectTracker<GLFrameBuffer>(::glBindFramebuffer, GL_FRAMEBUFFER)
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) }
var activeTexture = 0
@ -352,6 +351,9 @@ class StarboundClient : Closeable {
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()
init {
@ -448,61 +450,24 @@ class StarboundClient : Closeable {
}
}
fun newVBO() = BufferObject.VBO()
fun newEBO() = BufferObject.EBO()
fun newVBO() = BufferObject.VBO()
fun newVAO() = VertexArrayObject()
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)
builder.upload()
programs.flat.use()
programs.flat.color = color
programs.flat.transform = matrixStack.last()
programs.position.use()
programs.position.colorMultiplier = color
programs.position.worldMatrix = stack.last()
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 fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER)
@ -533,12 +498,12 @@ class StarboundClient : Closeable {
}
}
private fun updateViewportMatrixScreen(): Matrix4f {
return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f))
private fun updateViewportMatrixScreen(): Matrix3f {
return Matrix3f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat())
}
private fun updateViewportMatrixWorld(): Matrix4f {
return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f)
private fun updateViewportMatrixWorld(): Matrix3f {
return Matrix3f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat())
}
private val xMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer()
@ -764,12 +729,14 @@ class StarboundClient : Closeable {
clearColor = RGBAColor.SLATE_GRAY
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
matrixStack.clear(viewportMatrixWorld)
stack.clear(Matrix3f.identity())
matrixStack.push().last()
.translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира
val viewMatrix = viewportMatrixWorld.copy()
.translate(viewportWidth / 2f, viewportHeight / 2f) // центр экрана + координаты отрисовки мира
.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) {
lambda.invoke(layers)
@ -786,7 +753,7 @@ class StarboundClient : Closeable {
layers = layers,
size = viewportRectangle)
layers.render(matrixStack)
layers.render()
val viewportLightingMem = viewportLightingMem
@ -818,7 +785,7 @@ class StarboundClient : Closeable {
blendFunc = BlendFunc.MULTIPLY_BY_SRC
quadTexture(viewportLightingTexture) {
/*quadTexture(viewportLightingTexture) {
it.quad(
(viewportCellX).toFloat(),
(viewportCellY).toFloat(),
@ -826,7 +793,7 @@ class StarboundClient : Closeable {
(viewportCellY + viewportCellHeight).toFloat(),
QuadTransformers.uv()
)
}
}*/
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
}
@ -836,11 +803,9 @@ class StarboundClient : Closeable {
for (lambda in onPostDrawWorld) {
lambda.invoke()
}
matrixStack.pop()
}
matrixStack.clear(viewportMatrixScreen)
regularShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
val thisTime = System.currentTimeMillis()
@ -851,22 +816,24 @@ class StarboundClient : Closeable {
alpha = (finishStartupRendering - thisTime) / 1000f
}
matrixStack.push()
matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat())
stack.push()
stack.last().translate(y = viewportHeight.toFloat())
var shade = 255
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))
matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f)
stack.last().translate(y = -size.height * 1.2f)
if (shade > 120) {
shade -= 10
}
}
matrixStack.pop()
stack.pop()
}
stack.clear(Matrix3f.identity())
for (fn in onDrawGUI) {
fn.invoke()
}
@ -876,6 +843,7 @@ class StarboundClient : Closeable {
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("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.glfwPollEvents()

View File

@ -5,23 +5,26 @@ import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.client.StarboundClient
import java.nio.ByteBuffer
sealed class BufferObject(val glType: Int) : GLObject() {
sealed class BufferObject : GLObject() {
final override val client = StarboundClient.current()
final override val pointer = glGenBuffers()
abstract val glType: Int
init {
checkForGLError("Creating Vertex Buffer Object")
client.registerCleanable(this, ::glDeleteBuffers, pointer)
override fun bind() {
// do nothing
}
fun bufferData(data: ByteBuffer, usage: Int): BufferObject {
override fun unbind() {
// do nothing
}
open fun bufferData(data: ByteBuffer, usage: Int): BufferObject {
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject {
open fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject {
client.ensureSameThread()
if (length > data.remaining().toLong()) {
@ -34,51 +37,103 @@ sealed class BufferObject(val glType: Int) : GLObject() {
return this
}
fun bufferData(data: IntArray, usage: Int): BufferObject {
open fun bufferData(data: IntArray, usage: Int): BufferObject {
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: FloatArray, usage: Int): BufferObject {
open fun bufferData(data: FloatArray, usage: Int): BufferObject {
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: DoubleArray, usage: Int): BufferObject {
open fun bufferData(data: DoubleArray, usage: Int): BufferObject {
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
fun bufferData(data: LongArray, usage: Int): BufferObject {
open fun bufferData(data: LongArray, usage: Int): BufferObject {
client.ensureSameThread()
glNamedBufferData(pointer, data, usage)
checkForGLError()
return this
}
class VBO : BufferObject(GL_ARRAY_BUFFER) {
override fun bind() {
client.vbo = this
class EBO : BufferObject() {
override val glType: Int
get() = GL_ELEMENT_ARRAY_BUFFER
override val pointer: Int = glCreateBuffers()
init {
checkForGLError("Creating Element Buffer Object")
client.registerCleanable(this, ::glDeleteBuffers, pointer)
}
override fun unbind() {
if (client.vbo == this) client.vbo = null
override fun bufferData(data: ByteBuffer, usage: Int): EBO {
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) {
override fun bind() {
client.ebo = this
class VBO : BufferObject() {
override val glType: Int
get() = GL_ARRAY_BUFFER
override val pointer: Int = glCreateBuffers()
init {
checkForGLError("Creating Vertex Buffer Object")
client.registerCleanable(this, ::glDeleteBuffers, pointer)
}
override fun unbind() {
if (client.ebo == this) client.ebo = null
override fun bufferData(data: ByteBuffer, usage: Int): VBO {
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.*
enum class GLType(
val identity: Int,
val typeIndentity: Int,
val type: Int,
val elementType: Int,
val byteSize: Int,
val logicalSize: Int,
val elementSize: Int,
// location(position) width
val width: Int
) {
INT(GL_INT, GL_INT, 4, 1),
UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1),
FLOAT(GL_FLOAT, GL_FLOAT, 4, 1),
DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1),
INT(GL_INT, GL_INT, 4, 1, 1),
UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1, 1),
FLOAT(GL_FLOAT, GL_FLOAT, 4, 1, 1),
DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1, 1),
VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2),
VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3),
VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4),
VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2, 1),
VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3, 1),
VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4, 1),
VEC2I(GL_INT_VEC2, GL_INT, 8, 2),
VEC3I(GL_INT_VEC3, GL_INT, 12, 3),
VEC4I(GL_INT_VEC4, GL_INT, 16, 4),
VEC2I(GL_INT_VEC2, GL_INT, 8, 2, 1),
VEC3I(GL_INT_VEC3, GL_INT, 12, 3, 1),
VEC4I(GL_INT_VEC4, GL_INT, 16, 4, 1),
MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2),
MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3),
MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4),
MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2, 2),
MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3, 3),
MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4, 4),
}

View File

@ -1,17 +1,39 @@
package ru.dbotthepony.kstarbound.client.gl
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import org.lwjgl.opengl.GL45.*
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import java.util.BitSet
class VertexArrayObject : GLObject() {
override val client = StarboundClient.current()
override val pointer = glGenVertexArrays()
override val pointer = glCreateVertexArrays()
init {
checkForGLError()
checkForGLError("Creating Vertex Array Object")
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() {
client.vao = this
}
@ -21,18 +43,89 @@ class VertexArrayObject : GLObject() {
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()
glVertexAttribPointer(position, size, type, normalize, stride, offset)
checkForGLError()
require(index >= 0) { "Invalid bind index: $index" }
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
}
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 {
client.ensureSameThread()
glEnableVertexArrayAttrib(pointer, position)
//glEnableVertexAttribArray(position)
checkForGLError()
if (!enabledAttributes[position]) {
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
}
}

View File

@ -1,5 +1,6 @@
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.glCompileShader
import org.lwjgl.opengl.GL20.glCreateShader
@ -33,9 +34,20 @@ class GLShader(body: String, type: Int) {
glGetShaderiv(pointer, GL_COMPILE_STATUS, result)
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))
}
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.gl.GLObject
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.IStruct3f
import ru.dbotthepony.kvector.api.IStruct4f
@ -26,8 +26,15 @@ import kotlin.reflect.KProperty
open class GLShaderProgram(
shaders: Iterable<GLShader>,
val attributes: GLAttributeList
val attributes: VertexAttributes
) : 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 pointer = glCreateProgram()
@ -42,14 +49,12 @@ open class GLShaderProgram(
glLinkProgram(pointer)
val success = intArrayOf(0)
glGetProgramiv(pointer, GL_LINK_STATUS, success)
if (success[0] == 0) {
if (glGetProgrami(pointer, GL_LINK_STATUS) == 0)
throw ShaderLinkException(glGetProgramInfoLog(pointer))
}
glGetError()
client.addShaderProgram(this)
}
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}")
}
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 {
client.ensureSameThread()
require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" }
}
val location = glGetUniformLocation(pointer, name)
val isMissing = location == -1
init {
if (location == -1)
if (isMissing && !allowMissing)
throw NoSuchElementException("Program ${this@GLShaderProgram} does not have uniform with name $name")
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
set(value) {
client.ensureSameThread()
if (isMissing) return
if (field != 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 v1 = 0f
override var value: IStruct2f = Vector2f.ZERO
set(value) {
client.ensureSameThread()
if (isMissing) return
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 v1 = 0f
private var v2 = 0f
@ -164,6 +172,7 @@ open class GLShaderProgram(
override var value: IStruct3f = Vector3f.ZERO
set(value) {
client.ensureSameThread()
if (isMissing) return
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 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()
set(value) {
client.ensureSameThread()
if (isMissing) return
if (field != value) {
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()
override var value: Matrix4f = Matrix4f.zero()
set(value) {
client.ensureSameThread()
if (isMissing) return
buff4x4.position(0)
value.storeRowColumn(buff4x4)
value.storeColumnRow(buff4x4)
buff4x4.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 v1 = 0f
private var v2 = 0f
@ -230,6 +241,7 @@ open class GLShaderProgram(
override var value: IStruct4f = Vector4f.ZERO
set(value) {
client.ensureSameThread()
if (isMissing) return
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
set(value) {
client.ensureSameThread()
if (isMissing) return
if (field != value) {
glProgramUniform1i(pointer, location, value)

View File

@ -1,125 +1,50 @@
package ru.dbotthepony.kstarbound.client.gl.shader
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.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
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> {
val client = StarboundClient.current()
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 transform by F4x4Uniform("transform")
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
}
var transform by F3x3Uniform("transform")
val builder = StreamVertexBuilder(attributes, GeometryType.QUADS)
}
class GLPrograms {
val tile = GLTileProgram()
val font = GLFontProgram()
val flat = GLFlatProgram()
val flatColor = GLFlatColorProgram()
val position = UberShader.Builder().build()
val positionTexture = UberShader.Builder().withTexture().build()
val positionColor = UberShader.Builder().withColor().build()
val tile = UberShader.Builder().withTexture().withHueShift().build()
val font = FontProgram()
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)),
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
*/
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_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(
attributes: GLAttributeList,
type: GeometryType,
attributes: VertexAttributes,
type: GeometryType? = null,
initialCapacity: Int = 64,
) {
val state = StarboundClient.current()
@ -21,15 +21,8 @@ class StreamVertexBuilder(
private val ebo = BufferObject.EBO()
init {
vao.bind()
vbo.bind()
ebo.bind()
attributes.apply(vao, true)
vao.unbind()
vbo.unbind()
ebo.unbind()
vao.elementBuffer = ebo
vao.bindAttributes(vbo, attributes)
}
fun upload(drawType: Int = GL45.GL_DYNAMIC_DRAW) {
@ -43,22 +36,6 @@ class StreamVertexBuilder(
bind()
GL45.glDrawElements(primitives, builder.indexCount, builder.indexType, 0L)
checkForGLError()
}
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)
unbind()
}
}

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_SHORT
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.kvector.util2d.AABB
import ru.dbotthepony.kvector.api.IStruct2f
import ru.dbotthepony.kvector.api.IStruct4f
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.cos
import kotlin.math.sin
private fun interface IndexWriter {
fun write(buffer: ByteBuffer, value: Int)
@ -50,11 +48,11 @@ private fun indexSize(type: Int): Int {
* Загрузка в память видеокарты происходит напрямую из буферов, через метод [upload]
*/
class VertexBuilder(
val attributes: GLAttributeList,
val attributes: VertexAttributes,
val defaultMode: GeometryType? = null,
initialCapacity: Int = 64
) {
constructor(attributes: GLAttributeList, initialCapacity: Int) : this(attributes, null, initialCapacity)
constructor(attributes: VertexAttributes, initialCapacity: Int) : this(attributes, null, initialCapacity)
init {
require(initialCapacity > 0) { "Invalid capacity: $initialCapacity" }
@ -73,14 +71,13 @@ class VertexBuilder(
var indexSize: Int = indexSize(indexType)
private set
private var vertexMemory = ByteBuffer.allocateDirect(vertexCapacity * attributes.stride).also { it.order(ByteOrder.LITTLE_ENDIAN) }
private var indexMemory = ByteBuffer.allocateDirect(indexCapacity * indexSize).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 = ByteBuffer.allocateDirect(indexCapacity * indexSize).also { it.order(ByteOrder.LITTLE_ENDIAN) }
private var indexWriter = writer(indexType)
private var inVertex = false
private var attributeIndex = 0
private var vertexAttributes = 0L
private var elementIndexOffset = 0
private var elementVertices = 0
var vertexCount = 0 // distinct vertices
@ -113,7 +110,7 @@ class VertexBuilder(
val indexSize = indexSize(indexType)
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)
vertexMemory.put(this.vertexMemory)
@ -171,7 +168,13 @@ class 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" }
this.mode = mode
ensureCapacity()
@ -183,8 +186,8 @@ class VertexBuilder(
fun begin(mode: GeometryType? = defaultMode): VertexBuilder {
this.mode = mode
inVertex = false
attributeIndex = 0
elementIndexOffset = 0
vertexAttributes = 0L
vertexCount = 0
indexCount = 0
elementCount = 0
@ -194,33 +197,7 @@ class VertexBuilder(
return this
}
private fun checkBounds() {
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) {
fun upload(vertices: BufferObject.VBO, elements: BufferObject.EBO, drawType: Int = GL45.GL_DYNAMIC_DRAW) {
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)" }
@ -231,8 +208,8 @@ class VertexBuilder(
vertexMemory.position(0)
indexMemory.position(0)
vbo.bufferData(vertexMemory, drawType, length = vertexPos.toLong())
ebo.bufferData(indexMemory, drawType, length = elementPos.toLong())
vertices.bufferData(vertexMemory, drawType, length = vertexPos.toLong())
elements.bufferData(indexMemory, drawType, length = elementPos.toLong())
vertexMemory.position(vertexPos)
indexMemory.position(elementPos)
@ -244,8 +221,11 @@ class VertexBuilder(
val mode = mode!!
inVertex = false
if (attributeIndex != attributes.size) {
throw IllegalStateException("Unfinished vertex, we are at $attributeIndex, while we have ${attributes.size} attributes")
if (vertexAttributes != attributes.allFlags) {
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++
@ -271,171 +251,63 @@ class VertexBuilder(
checkNotNull(mode) { "No builder mode is set" }
inVertex = true
attributeIndex = 0
vertexAttributes = 0L
return this
}
fun pushVec4f(x: Float, y: Float, z: Float, w: 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 {
fun vertex(x: Float, y: Float): VertexBuilder {
vertex()
pushVec4f(x0, y0, x1, y1)
pushVec2f(0f, 0f)
position(x, y)
return this
}
vertex()
pushVec4f(x0, y0, x1, y1)
pushVec2f(0f, 1f)
vertex()
pushVec4f(x0, y0, x1, y1)
pushVec2f(1f, 1f)
vertex()
pushVec4f(x0, y0, x1, y1)
pushVec2f(1f, 0f)
private fun pushFloat(attr: VertexAttributes.Attribute, x: Float): VertexBuilder {
vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
vertexMemory.putFloat(x)
vertexAttributes = vertexAttributes or attr.flag
return this
}
fun dfShadowLine(x0: Float, y0: Float, x1: Float, y1: Float): VertexBuilder {
shadowLine(x0, y0, x1, y1)
shadowLine(x1, y1, x0, y0)
return this
}
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()
private fun pushFloat2(attr: VertexAttributes.Attribute, x: Float, y: Float): VertexBuilder {
vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
vertexMemory.putFloat(x)
vertexMemory.putFloat(y)
vertexAttributes = vertexAttributes or attr.flag
return this
}
fun quadRotated(
x0: Float,
y0: Float,
x1: Float,
y1: 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()
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()
private fun pushFloat4(attr: VertexAttributes.Attribute, x: Float, y: Float, z: Float, w: Float): VertexBuilder {
vertexMemory.position(vertexCount * attributes.vertexStride + attr.relativeOffset)
vertexMemory.putFloat(x)
vertexMemory.putFloat(y)
vertexMemory.putFloat(z)
vertexMemory.putFloat(w)
vertexAttributes = vertexAttributes or attr.flag
return this
}
fun quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): VertexBuilder {
return quad(
aabb.mins.x.toFloat(),
aabb.mins.y.toFloat(),
aabb.maxs.x.toFloat(),
aabb.maxs.y.toFloat(),
lambda
)
fun position(x: Float, y: Float): VertexBuilder {
return pushFloat2(requireNotNull(attributes[VertexAttributeType.POSITION]) { "Vertex format does not have position attribute" }, x, y)
}
fun quadZ(
x0: Float,
y0: Float,
x1: Float,
y1: Float,
z: Float,
lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM
): VertexBuilder {
check(mode?.elements == 4) { "Currently building $mode" }
fun position(value: IStruct2f) = position(value.component1(), value.component2())
lambda(vertex().pushVec3f(x0, y0, z), 0).end()
lambda(vertex().pushVec3f(x1, y0, z), 1).end()
lambda(vertex().pushVec3f(x0, y1, z), 2).end()
lambda(vertex().pushVec3f(x1, y1, z), 3).end()
return this
fun uv(x: Float, y: Float): VertexBuilder {
return pushFloat2(requireNotNull(attributes[VertexAttributeType.UV]) { "Vertex format does not have texture UV attribute" }, x, y)
}
fun quadRotatedZ(
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" }
fun uv(value: IStruct2f) = position(value.component1(), value.component2())
val s = sin(angle).toFloat()
val c = cos(angle).toFloat()
fun color(x: Float, y: Float, z: Float, w: Float): VertexBuilder {
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()
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()
fun color(value: IStruct4f) = color(value.component1(), value.component2(), value.component3(), value.component4())
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.kstarbound.PIXELS_IN_STARBOUND_UNIT
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.Vector2d
import kotlin.math.cos
@ -12,6 +16,8 @@ import kotlin.math.sin
class Box2DRenderer : IDebugDraw {
val state = StarboundClient.current()
private val identity = Matrix3f.identity()
override var drawShapes: Boolean = false
override var drawJoints: Boolean = false
override var drawAABB: Boolean = false
@ -19,25 +25,26 @@ class Box2DRenderer : IDebugDraw {
override var drawPairs: Boolean = false
override var drawCenterOfMess: Boolean = false
private val builder = StreamVertexBuilder(VertexAttributes.POSITION)
override fun drawPolygon(vertices: List<Vector2d>, color: RGBAColor) {
require(vertices.size > 1) { "Vertex list had only ${vertices.size} namings in it" }
val builder = state.flat2DLines
builder.builder.begin()
for (i in vertices.indices) {
val current = vertices[i]
val next = vertices[(i + 1) % vertices.size]
builder.builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat())
builder.builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat())
builder.builder.vertex(current.x.toFloat(), current.y.toFloat())
builder.builder.vertex(next.x.toFloat(), next.y.toFloat())
}
builder.upload()
state.programs.flat.use()
state.programs.flat.color = color
state.programs.flat.transform = state.matrixStack.last()
state.programs.position.use()
state.programs.position.colorMultiplier = color
state.programs.position.worldMatrix = state.stack.last()
state.programs.position.modelMatrix = identity
builder.draw(GL_LINES)
}
@ -45,25 +52,24 @@ class Box2DRenderer : IDebugDraw {
private fun drawSolid(vertices: List<Vector2d>, color: RGBAColor) {
require(vertices.size >= 3) { "Vertex list had only ${vertices.size} namings in it" }
val builder = state.flat2DTriangles
builder.builder.begin()
builder.builder.begin(GeometryType.TRIANGLES)
val zero = vertices[0]
for (i in 1 until vertices.size) {
val current = vertices[i]
val next = vertices[(i + 1) % vertices.size]
builder.builder.vertex().pushVec2f(zero.x.toFloat(), zero.y.toFloat())
builder.builder.vertex().pushVec2f(current.x.toFloat(), current.y.toFloat())
builder.builder.vertex().pushVec2f(next.x.toFloat(), next.y.toFloat())
builder.builder.vertex(zero.x.toFloat(), zero.y.toFloat())
builder.builder.vertex(current.x.toFloat(), current.y.toFloat())
builder.builder.vertex(next.x.toFloat(), next.y.toFloat())
}
builder.upload()
state.programs.flat.use()
state.programs.flat.color = color
state.programs.flat.transform = state.matrixStack.last()
state.programs.position.use()
state.programs.position.colorMultiplier = color
state.programs.position.worldMatrix = state.stack.last()
state.programs.position.modelMatrix = identity
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.gl.*
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.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
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
private fun breakLines(text: String): List<String> {
@ -91,7 +90,6 @@ class Font(
color: RGBAColor = RGBAColor.WHITE,
scale: Float = 1f,
stack: Matrix4fStack = state.matrixStack,
): TextSize {
if (text.isEmpty())
return TextSize(0f, 0f)
@ -102,20 +100,22 @@ class Font(
val totalX = totalSize.width
val totalY = totalSize.height
stack.push()
val model = Matrix3f.identity()
when (alignY) {
TextAlignY.TOP -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale + y)
TextAlignY.CENTER -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale - totalY * scale / 2f + y)
TextAlignY.BOTTOM -> stack.last().translateWithMultiplication(x = x, y = lineHeight * scale - totalY * scale + y)
TextAlignY.TOP -> model.translate(x = x, y = lineHeight * scale + y)
TextAlignY.CENTER -> model.translate(x = x, y = lineHeight * scale - totalY * scale / 2f + y)
TextAlignY.BOTTOM -> model.translate(x = x, y = lineHeight * scale - totalY * scale + y)
}
if (scale != 1f)
stack.last().scale(x = scale, y = scale)
model.scale(x = scale, y = scale)
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.programs.font.texture = 0
val space = getGlyph(' ')
@ -123,7 +123,7 @@ class Font(
for (line in text) {
if (line == "") {
stack.last().translateWithMultiplication(y = lineHeight)
model.translate(y = lineHeight)
continue
}
@ -134,12 +134,12 @@ class Font(
TextAlignX.CENTER -> {
movedX = totalX / 2f - lineWidth(line, space) / 2f
stack.last().translateWithMultiplication(x = movedX)
model.translate(x = movedX)
}
TextAlignX.RIGHT -> {
movedX = -lineWidth(line, space)
stack.last().translateWithMultiplication(x = movedX)
model.translate(x = movedX)
}
}
@ -150,10 +150,10 @@ class Font(
if (chr == '\t') {
if (lineGlyphs % 4 == 0) {
advancedX += space.advanceX * 4
stack.last().translateWithMultiplication(x = space.advanceX * 4)
model.translate(x = space.advanceX * 4)
} else {
advancedX += space.advanceX * (lineGlyphs % 4)
stack.last().translateWithMultiplication(x = space.advanceX * (lineGlyphs % 4))
model.translate(x = space.advanceX * (lineGlyphs % 4))
}
lineGlyphs += lineGlyphs % 4
@ -161,17 +161,16 @@ class Font(
}
val glyph = getGlyph(chr)
glyph.render(stack)
glyph.render(model)
lineWidth += glyph.advanceX
lineGlyphs++
}
advancedX = advancedX.coerceAtLeast(lineWidth)
stack.last().translateWithMultiplication(x = -lineWidth - movedX, y = lineHeight)
model.translate(x = -lineWidth - movedX, y = lineHeight)
}
state.vao = null
stack.pop()
return TextSize(totalX * scale, totalY * scale)
}
@ -188,7 +187,6 @@ class Font(
color: RGBAColor = RGBAColor.WHITE,
scale: Float = 1f,
stack: Matrix4fStack = state.matrixStack,
): TextSize {
return render(
breakLines(text),
@ -197,7 +195,6 @@ class Font(
alignX = alignX,
alignY = alignY,
scale = scale,
stack = stack,
color = color,
)
}
@ -210,10 +207,10 @@ class Font(
if (chr == '\t') {
if (lineGlyphs % 4 == 0) {
lineWidth += space.advanceX * 4
state.matrixStack.last().translateWithMultiplication(x = space.advanceX * 4)
state.stack.last().translate(x = space.advanceX * 4)
} else {
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
@ -258,9 +255,7 @@ class Font(
val advanceX: Float
val advanceY: Float
private val vbo: BufferObject.VBO? // все три указателя должны хранится во избежание утечки
private val ebo: BufferObject.EBO? // все три указателя должны хранится во избежание утечки
private val vao: VertexArrayObject? // все три указателя должны хранится во избежание утечки
private val vao: VertexArrayObject?
private val indexCount: Int
@ -301,54 +296,48 @@ class Font(
glPixelStorei(GL_UNPACK_ALIGNMENT, 4)
vao = state.newVAO()
ebo = state.newEBO()
vbo = state.newVBO()
val ebo = state.newEBO()
val vbo = state.newVBO()
vao.bind()
ebo.bind()
vbo.bind()
vao.elementBuffer = ebo
vao.bindAttributes(vbo, VertexAttributes.POSITION_UV)
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.attributes.apply(vao, true)
indexCount = builder.indexCount
elementIndexType = builder.indexType
vao.unbind()
ebo.unbind()
vbo.unbind()
} else {
isEmpty = true
indexCount = 0
elementIndexType = 0
vao = null
vbo = null
ebo = null
texture = null
}
}
fun render(stack: Matrix4fStack) {
fun render(model: Matrix3f) {
if (isEmpty) {
stack.last().translateWithMultiplication(advanceX)
model.translate(advanceX)
return
}
vao!!.bind()
stack.last().translateWithMultiplication(bearingX, -bearingY)
model.translate(bearingX, -bearingY)
texture!!.bind()
state.programs.font.transform = stack.last()
state.programs.font.modelMatrix = model
glDrawElements(GL_TRIANGLES, indexCount, elementIndexType, 0L)
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.Long2ObjectOpenHashMap
import ru.dbotthepony.kvector.arrays.Matrix3fStack
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import java.util.function.Function
/**
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности
*/
class LayeredRenderer {
private val layers = Long2ObjectAVLTreeMap<ArrayList<(Matrix4fStack) -> Unit>>()
private val layersHash = Long2ObjectOpenHashMap<ArrayList<(Matrix4fStack) -> Unit>>()
private val layers = Long2ObjectAVLTreeMap<ArrayList<() -> Unit>>()
fun add(layer: Long, renderer: (Matrix4fStack) -> Unit) {
var list = layersHash[layer]
if (list == null) {
list = ArrayList()
layers[layer] = list
layersHash[layer] = list
}
list.add(renderer)
fun add(layer: Long, renderer: () -> Unit) {
layers.computeIfAbsent(layer, Function { ArrayList() }).add(renderer)
}
fun render(stack: Matrix4fStack) {
fun render() {
for (list in layers.values) {
for (renderer in list) {
renderer.invoke(stack)
renderer.invoke()
}
}
layers.clear()
}
}

View File

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

View File

@ -7,7 +7,7 @@ abstract class RenderConfig<out T : GLShaderProgram>(val program: T) {
val client get() = program.client
open val initialBuilderCapacity: Int get() = 64
open fun setup(transform: Matrix4f = client.matrixStack.last()) {
open fun setup() {
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.client.StarboundClient
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.defs.tile.*
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i
import kotlin.collections.HashMap
@ -27,6 +27,7 @@ class TileRenderers(val client: StarboundClient) {
private val background = HashMap<GLTexture2D, Config>()
private val matCache = HashMap<String, TileRenderer>()
private val modCache = HashMap<String, TileRenderer>()
private val identity = Matrix3f.identity()
fun getMaterialRenderer(defName: String): TileRenderer {
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
get() = 1024
override fun setup(transform: Matrix4f) {
super.setup(transform)
override fun setup() {
super.setup()
client.activeTexture = 0
client.depthTest = false
program.texture = 0
program.texture0 = 0
texture.bind()
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST
program.transform = transform
program.color = color
program.worldMatrix = client.stack.last()
program.modelMatrix = identity
program.colorMultiplier = color
}
override fun uninstall() {}
}
fun foreground(texture: GLTexture2D): RenderConfig<GLTileProgram> {
fun foreground(texture: GLTexture2D): RenderConfig<UberShader> {
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) }
}
@ -146,7 +148,13 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
val (u0, v0) = texture!!.pixelToUV(mins)
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(
@ -222,7 +230,6 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
}
companion object {
const val Z_LEVEL = 10f
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -9,9 +9,6 @@ class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChu
private val def = entity.def
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)
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) {
layers.add(zLevel + RenderLayer.BackgroundTile.index) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.render(it.last())
it.pop()
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
baked.render()
client.stack.pop()
}
}
for ((baked, zLevel) in foreground.bakedMeshes) {
layers.add(zLevel + RenderLayer.ForegroundTile.index) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.render(it.last())
it.pop()
client.stack.push().last().translate(renderOrigin.x, renderOrigin.y)
baked.render()
client.stack.pop()
}
}
if (liquidMesh.isNotEmpty()) {
/*if (liquidMesh.isNotEmpty()) {
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
program.use()
program.transform = it.last()
program.transform = client.stack.last()
for ((mesh, color) in liquidMesh) {
program.baselineColor = color
mesh.render()
}
it.pop()
client.stack.pop()
}
}
}*/
}
}
@ -266,24 +269,21 @@ class ClientWorld(
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) {
//layers.add(RenderLayer.Object.index) {
layers.add(obj.orientation?.renderLayer ?: continue) { m ->
layers.add(obj.orientation?.renderLayer ?: continue) {
client.quadWireframe {
it.quad(
obj.pos.x.toFloat(),
obj.pos.y.toFloat(),
obj.pos.x + 1f,
obj.pos.y + 1f,
)
it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat())
it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat())
it.vertex(obj.pos.x.toFloat() + 1f, obj.pos.y.toFloat() + 1f)
it.vertex(obj.pos.x.toFloat(), obj.pos.y.toFloat() + 1f)
}
obj.drawables.forEach {
val (x, y) = obj.imagePosition
it.render(
client,
x = obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf,
y = obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf
)
client.stack.push().last()
.translate(obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf)
it.render(client)
client.stack.pop()
}
}

View File

@ -10,18 +10,23 @@ import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
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.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.io.json.consumeNull
import ru.dbotthepony.kstarbound.math.LineF
import ru.dbotthepony.kstarbound.util.Either
import ru.dbotthepony.kstarbound.util.contains
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector3f
import kotlin.math.sin
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(
val line: LineF,
val width: Float,
@ -29,7 +34,11 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
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) {
TODO("Not yet implemented")
}
override fun flop(): Drawable {
TODO("Not yet implemented")
}
}
@ -40,15 +49,18 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
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) {
TODO("Not yet implemented")
}
override fun flop(): Drawable {
TODO("Not yet implemented")
}
}
class Image(
val path: SpriteReference,
val transform: Matrix3f,
val centered: Boolean,
val transform: Either<Matrix3f, Transformations>,
position: Vector2f = Vector2f.ZERO,
color: RGBAColor = RGBAColor.WHITE,
fullbright: Boolean = false
@ -60,34 +72,73 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
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 texture = client.loadTexture(path.imagePath.value!!)
val program = client.programs.positionTexture
if (centered) {
client.quadTexture(texture) {
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))
program.modelMatrix = transform.map({ it }, {
val mat = Matrix3f.identity()
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) {
it.quad(x, y, x + sprite.width / PIXELS_IN_STARBOUND_UNITf, y + sprite.height / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite))
if (it.rotation != 0f) {
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) {
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 {
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 {
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 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 transformations = gson.getAdapter(Transformations::class.java)
override fun write(out: JsonWriter?, value: Drawable?) {
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)
} else if ("image" in value) {
val image = images.fromJsonTree(value["image"])
val mat = Matrix3f.identity()
if ("transformation" in value) {
val mat = Matrix3f.identity()
val array = value["transformation"].asJsonArray
// 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.r21 = row2.y
mat.r22 = row2.z
return Image(
image,
Either.left(mat),
position,
color,
fullbright
)
} else {
if ("rotation" in value) {
LOGGER.warn("Rotation is not supported yet (required by ${image.raw})")
}
if ("mirrored" in value && value["mirrored"].asBoolean) {
mat.scale(-1f, -1f)
}
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,
Either.right(transformations.fromJsonTree(value)),
position,
color,
fullbright
)
}
return Image(image, mat, value["centered"]?.asBoolean ?: false, position, color, fullbright)
} else {
return Empty(position, color, fullbright)
}

View File

@ -130,10 +130,22 @@ data class ObjectOrientation(
if ("imageLayers" in obj) {
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 {
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

View File

@ -7,59 +7,35 @@ import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
*
* JSON адаптер реализуется через [EitherTypeAdapter]
*/
sealed class Either<L, R> {
class Left<L, R>(val value: L) : Either<L, R>() {
override val isLeft: Boolean
get() = true
override val isRight: Boolean
get() = false
class Either<L, R> private constructor(val left: KOptional<L>, val right: KOptional<R>) {
val isLeft: Boolean get() = left.isPresent
val isRight: Boolean get() = right.isPresent
override fun <T> map(left: (L) -> T, right: (R) -> T): T {
return left.invoke(this.value)
inline fun <T> map(left: (L) -> T, right: (R) -> T): T {
this.left.ifPresent {
return left.invoke(it)
}
override fun left(): L {
return value
}
override fun right(): R {
throw NoSuchElementException()
}
return right.invoke(this.right.value)
}
class Right<L, R>(val value: R) : Either<L, R>() {
override val isLeft: Boolean
get() = false
override val isRight: Boolean
get() = true
override fun <T> map(left: (L) -> T, right: (R) -> T): T {
return right.invoke(value)
inline fun <NL, NR> flatMap(left: (L) -> NL, right: (R) -> NR): Either<NL, NR> {
this.left.ifPresent {
return left(left.invoke(it))
}
override fun left(): L {
throw NoSuchElementException()
}
override fun right(): R {
return value
}
return right(right.invoke(this.right.value))
}
abstract val isLeft: Boolean
abstract val isRight: Boolean
abstract fun <T> map(left: (L) -> T, right: (R) -> T): T
/**
* @throws NoSuchElementException
*/
abstract fun left(): L
fun left(): L = left.value
/**
* @throws NoSuchElementException
*/
abstract fun right(): R
fun right(): R = right.value
inline fun leftOrElse(orElse: () -> L): L {
if (isLeft)
@ -77,11 +53,11 @@ sealed class Either<L, R> {
companion object {
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> {
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.TypeAdapter
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: String) { add(key, JsonPrimitive(value)) }
@ -147,3 +148,17 @@ inline fun <reified T : JsonElement> JsonObject.get(key: String, orElse: () -> T
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
// 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
class HashTableInterner<T : Any>(private val segmentBits: Int) : Interner<T> {
companion object {

View File

@ -10,6 +10,9 @@ fun <T> KOptional(value: T) = KOptional.of(value)
* in more elegant solution than handling nullable Optionals
*/
class KOptional<T> private constructor(private val _value: T, val isPresent: Boolean) {
/**
* @throws NoSuchElementException
*/
val value: T get() {
if (isPresent) {
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;
uniform mat4 transform;
uniform mat3 transform;
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;
}

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;
uniform mat3 viewMatrix; // camera
uniform mat3 worldMatrix; // world position
uniform mat3 modelMatrix; // mesh/local transformations
uniform mat3 viewMatrix; // projection (viewport) + camera
uniform mat3 worldMatrix; // matrix stack
uniform mat3 modelMatrix; // local transformations
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 = 1) in vec4 color;
uniform mat3 viewMatrix; // camera
uniform mat3 worldMatrix; // world position
uniform mat3 modelMatrix; // mesh/local transformations
uniform mat3 viewMatrix; // projection (viewport) + camera
uniform mat3 worldMatrix; // matrix stack
uniform mat3 modelMatrix; // local transformations
out vec4 vertexColor;
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;
}