New shader render pipeline
This commit is contained in:
parent
62dfc63839
commit
339891b6e2
@ -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")
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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") }
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -1,9 +0,0 @@
|
||||
|
||||
#version 330
|
||||
|
||||
uniform vec4 color;
|
||||
out vec4 color_out;
|
||||
|
||||
void main() {
|
||||
color_out = color;
|
||||
}
|
@ -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);
|
||||
}
|
59
src/main/resources/shaders/fragment/configurable.fsh
Normal file
59
src/main/resources/shaders/fragment/configurable.fsh
Normal 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;
|
||||
}
|
13
src/main/resources/shaders/fragment/font.fsh
Normal file
13
src/main/resources/shaders/fragment/font.fsh
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
38
src/main/resources/shaders/vertex/configurable.vsh
Normal file
38
src/main/resources/shaders/vertex/configurable.vsh
Normal 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
|
||||
}
|
18
src/main/resources/shaders/vertex/font.vsh
Normal file
18
src/main/resources/shaders/vertex/font.vsh
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user