diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 1e59bef8..9082fd81 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -3,16 +3,24 @@ package ru.dbotthepony.kstarbound import org.apache.logging.log4j.LogManager import org.lwjgl.Version import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose +import org.lwjgl.opengl.GL11.GL_LINES +import org.lwjgl.opengl.GL11.GL_RGBA +import org.lwjgl.opengl.GL46 import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.BlendFunc +import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer +import ru.dbotthepony.kstarbound.client.gl.GLTexture2D +import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.quad import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.entities.PlayerEntity +import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.ndouble.Vector2d import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File import java.util.zip.Inflater -import kotlin.math.roundToInt private val LOGGER = LogManager.getLogger() @@ -28,9 +36,9 @@ fun main() { Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) //Starbound.addPakPath(File("packed.pak")) - Starbound.initializeGame { finished, replaceStatus, status -> + /*Starbound.initializeGame { finished, replaceStatus, status -> client.putDebugLog(status, replaceStatus) - } + }*/ client.onTermination { Starbound.terminateLoading = true @@ -163,13 +171,90 @@ fun main() { //ent.position += Vector2d(y = 14.0, x = -10.0) ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0) - client.camera.pos.x = 578f - client.camera.pos.y = 695f + //client.camera.pos.x = 578f + //client.camera.pos.y = 695f client.onDrawGUI { client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f) client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) - client.gl.font.render("${client.camera.pos}", y = 140f, scale = 0.25f) + client.gl.font.render("${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) + } + + val lightmap = GLFrameBuffer(client.gl) + + lightmap.reattachTexture(client.viewportWidth, client.viewportHeight, GL_RGBA) + + client.onViewportChanged { width, height -> lightmap.reattachTexture(width, height, GL_RGBA) } + + client.onPostDrawWorld { + lightmap.bind() + GL46.glClear(GL46.GL_COLOR_BUFFER_BIT or GL46.GL_DEPTH_BUFFER_BIT) + //client.gl.programs.colorQuad.clearAlpha() + + client.gl.programs.light.use() + client.gl.programs.light.transform.set(client.gl.matrixStack.last) + // client.gl.programs.light.transform.set(Matrix4f.IDENTITY) + client.gl.programs.light.baselineColor.set(Color.WHITE) + + var builder = client.gl.programs.light.builder + + val (rX, rY) = client.screenToWorld(client.mouseCoordinatesF) + + builder.begin() + + //val size = 20f / client.settings.scale + val size = 10f + + builder.quad(rX - size, rY - size, rX + size, rY + size, QuadTransformers.uv()) + + builder.upload() + + //client.gl.blendFunc = BlendFunc( + // BlendFunc.Func.DST_ALPHA, + // BlendFunc.Func.ONE_MINUS_DST_ALPHA + //) + + builder.draw() + client.gl.blendFunc = BlendFunc.ALPHA_TEST + + client.gl.programs.lightOccluder.use() + client.gl.programs.lightOccluder.transform.set(client.gl.matrixStack.last) + + builder = client.gl.programs.lightOccluder.builder + + builder.begin() + + val lightPosition = client.screenToWorld(client.mouseCoordinatesF) + + builder.quad(0f, 0f, 2f, 2f) + builder.quad(-6f, 0f, -2f, 2f) + + client.gl.programs.lightOccluder.lightPosition.set(lightPosition) + + builder.upload() + + client.gl.blendFunc = BlendFunc.ONLY_ALPHA + builder.draw(GL_LINES) + client.gl.blendFunc = BlendFunc.ALPHA_TEST + + lightmap.unbind() + + client.gl.activeTexture = 0 + client.gl.texture2D = lightmap.texture + client.gl.programs.textureQuad.run(0) + + client.gl.programs.flat.use() + client.gl.programs.flat.color.set(Color.BLACK) + client.gl.programs.flat.transform.set(client.gl.matrixStack.last) + builder = client.gl.programs.flat.builder + + builder.begin() + + builder.quad(0f, 0f, 2f, 2f) + builder.quad(-6f, 0f, -2f, 2f) + + builder.upload() + builder.draw() } client.onPreDrawWorld { @@ -186,9 +271,9 @@ fun main() { client.input.addScrollCallback { _, x, y -> if (y > 0.0) { - client.settings.scale *= y.toFloat() * 2f + client.settings.zoom *= y.toFloat() * 2f } else if (y < 0.0) { - client.settings.scale /= -y.toFloat() * 2f + client.settings.zoom /= -y.toFloat() * 2f } } @@ -199,11 +284,11 @@ fun main() { //client.camera.pos.x = ent.position.x.toFloat() //client.camera.pos.y = ent.position.y.toFloat() - client.camera.pos.x += if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f - client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.x += if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f + client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f - client.camera.pos.y += if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f - client.camera.pos.y += if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.y += if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f + client.camera.pos.y += if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f //println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index 441f2472..2f96cac6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.gl.vertex.quad import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.EntityRenderer import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt index 14751b3c..c4bcbeaa 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt @@ -6,7 +6,7 @@ data class ClientSettings( * * Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране */ - var scale: Float = 2f, + var zoom: Float = 2f, var debugCollisions: Boolean = false, ) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt index 50ed9277..e9d02b0c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.quadZ import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer import ru.dbotthepony.kstarbound.client.render.renderLayeredList import ru.dbotthepony.kstarbound.defs.ParallaxPrototype diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 2e48ac17..a698fb1a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -10,16 +10,20 @@ import org.lwjgl.system.MemoryUtil import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.client.gl.BlendFunc import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.render.Camera -import ru.dbotthepony.kstarbound.client.render.TextAlignX import ru.dbotthepony.kstarbound.client.render.TextAlignY import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.Color +import ru.dbotthepony.kvector.vector.ndouble.Vector2d +import ru.dbotthepony.kvector.vector.nfloat.Vector2f import ru.dbotthepony.kvector.vector.nfloat.Vector3f +import java.nio.ByteBuffer +import java.nio.ByteOrder class StarboundClient : AutoCloseable { val window: Long @@ -35,10 +39,16 @@ class StarboundClient : AutoCloseable { var viewportHeight = 600 private set - var viewportMatrixGUI = updateViewportMatrixA() + /** + * Матрица преобразования экранных координат (в пикселях) в нормализованные координаты + */ + var viewportMatrixScreen = updateViewportMatrixScreen() private set - var viewportMatrixGame = updateViewportMatrixB() + /** + * Матрица преобразования мировых координат в нормализованные координаты + */ + var viewportMatrixWorld = updateViewportMatrixWorld() private set private val startupTextList = ArrayList() @@ -58,12 +68,69 @@ class StarboundClient : AutoCloseable { finishStartupRendering = System.currentTimeMillis() + 4000L } - private fun updateViewportMatrixA(): Matrix4f { - return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f) + private fun updateViewportMatrixScreen(): Matrix4f { + return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f)).toMatrix4f() } - private fun updateViewportMatrixB(): Matrix4f { - return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f) + private fun updateViewportMatrixWorld(): Matrix4f { + return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 1f, 100f).toMatrix4f() + } + + private val xMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer() + private val yMousePos = ByteBuffer.allocateDirect(8).also { it.order(ByteOrder.LITTLE_ENDIAN) }.asDoubleBuffer() + + val mouseCoordinates: Vector2d get() { + xMousePos.position(0) + yMousePos.position(0) + GLFW.glfwGetCursorPos(window, xMousePos, yMousePos) + xMousePos.position(0) + yMousePos.position(0) + + return Vector2d(xMousePos.get(), yMousePos.get()) + } + + val mouseCoordinatesF: Vector2f get() { + xMousePos.position(0) + yMousePos.position(0) + GLFW.glfwGetCursorPos(window, xMousePos, yMousePos) + xMousePos.position(0) + yMousePos.position(0) + + return Vector2f(xMousePos.get().toFloat(), yMousePos.get().toFloat()) + } + + /** + * Преобразует экранные координаты в мировые + */ + fun screenToWorld(x: Double, y: Double): Vector2d { + val relativeX = camera.pos.x - (viewportWidth / 2.0 - x) / PIXELS_IN_STARBOUND_UNIT / settings.zoom + val relativeY = (viewportHeight / 2.0 - y) / PIXELS_IN_STARBOUND_UNIT / settings.zoom + camera.pos.y + + return Vector2d(relativeX, relativeY) + } + + /** + * Преобразует экранные координаты в мировые + */ + fun screenToWorld(value: Vector2d): Vector2d { + return screenToWorld(value.x, value.y) + } + + /** + * Преобразует экранные координаты в мировые + */ + fun screenToWorld(x: Float, y: Float): Vector2f { + val relativeX = camera.pos.x - (viewportWidth / 2f - x) / PIXELS_IN_STARBOUND_UNITf / settings.zoom + val relativeY = (viewportHeight / 2f - y) / PIXELS_IN_STARBOUND_UNITf / settings.zoom + camera.pos.y + + return Vector2f(relativeX, relativeY) + } + + /** + * Преобразует экранные координаты в мировые + */ + fun screenToWorld(value: Vector2f): Vector2f { + return screenToWorld(value.x, value.y) } init { @@ -90,9 +157,13 @@ class StarboundClient : AutoCloseable { GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h -> viewportWidth = w viewportHeight = h - viewportMatrixGUI = updateViewportMatrixA() - viewportMatrixGame = updateViewportMatrixB() + viewportMatrixScreen = updateViewportMatrixScreen() + viewportMatrixWorld = updateViewportMatrixWorld() glViewport(0, 0, w, h) + + for (callback in onViewportChanged) { + callback.invoke(w, h) + } } val stack = MemoryStack.stackPush() @@ -134,7 +205,7 @@ class StarboundClient : AutoCloseable { gl.clearColor = Color.SLATE_GREY gl.blend = true - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + gl.blendFunc = BlendFunc.ALPHA_TEST } var frameRenderTime = 0.0 @@ -160,6 +231,12 @@ class StarboundClient : AutoCloseable { val settings = ClientSettings() + private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>() + + fun onViewportChanged(callback: (width: Int, height: Int) -> Unit) { + onViewportChanged.add(callback) + } + private val onDrawGUI = ArrayList<() -> Unit>() fun onDrawGUI(lambda: () -> Unit) { @@ -198,11 +275,11 @@ class StarboundClient : AutoCloseable { world?.think(frameRenderTime) glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) - gl.matrixStack.clear(viewportMatrixGame.toMutableMatrix4f()) + gl.matrixStack.clear(viewportMatrixWorld.toMutableMatrix4f()) gl.matrixStack.push() .translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира - .scale(x = settings.scale * PIXELS_IN_STARBOUND_UNITf, y = settings.scale * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера + .scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера .translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере for (lambda in onPreDrawWorld) { @@ -217,8 +294,8 @@ class StarboundClient : AutoCloseable { world?.render( AABB.rectangle( camera.pos.toDoubleVector(), - viewportWidth / settings.scale / PIXELS_IN_STARBOUND_UNIT, - viewportHeight / settings.scale / PIXELS_IN_STARBOUND_UNIT)) + viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT, + viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT)) for (lambda in onPostDrawWorld) { lambda.invoke() @@ -226,7 +303,7 @@ class StarboundClient : AutoCloseable { gl.matrixStack.pop() - gl.matrixStack.clear(viewportMatrixGUI.toMutableMatrix4f().translate(Vector3f(z = 2f))) + gl.matrixStack.clear(viewportMatrixScreen.toMutableMatrix4f()) val thisTime = System.currentTimeMillis() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt new file mode 100644 index 00000000..1449e6ee --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt @@ -0,0 +1,93 @@ +package ru.dbotthepony.kstarbound.client.gl + +import org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT0 +import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER +import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE +import org.lwjgl.opengl.GL30.GL_LINEAR +import org.lwjgl.opengl.GL30.GL_RGB +import org.lwjgl.opengl.GL30.GL_TEXTURE +import org.lwjgl.opengl.GL30.GL_TEXTURE_2D +import org.lwjgl.opengl.GL30.glCheckFramebufferStatus +import org.lwjgl.opengl.GL30.glFramebufferTexture2D +import org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus +import org.lwjgl.opengl.GL45.glNamedFramebufferTexture +import org.lwjgl.opengl.GL46 + +class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable { + init { + state.ensureSameThread() + } + + val pointer = GL46.glGenFramebuffers() + + init { + checkForGLError() + } + + val isComplete: Boolean get() { + state.ensureSameThread() + return glCheckNamedFramebufferStatus(pointer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE + } + + var texture: GLTexture2D? = null + private set + + fun attachTexture(width: Int, height: Int, format: Int = GL_RGB) { + if (texture != null) { + throw IllegalStateException("Already has texture attached") + } + + val texture = GLTexture2D(state, "framebuffer_$pointer") + texture.allocate(format, width, height) + + val old = state.framebuffer + bind() + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.pointer, 0) + checkForGLError() + this.texture = texture + texture.textureMinFilter = GL_LINEAR + state.framebuffer = old + } + + fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) { + texture?.close() + texture = null + attachTexture(width, height, format) + } + + fun bind() { + state.framebuffer = this + } + + fun bindWrite() { + state.writeFramebuffer = this + } + + fun bindRead() { + state.readFramebuffer = this + } + + fun unbind() { + if (state.writeFramebuffer == this) { + state.writeFramebuffer = null + } + + if (state.readFramebuffer == this) { + state.readFramebuffer = null + } + } + + private val cleanable = state.registerCleanable(this, GL46::glDeleteFramebuffers, "Framebuffer", pointer) + var isValid = true + private set + + override fun close() { + state.ensureSameThread() + + if (!isValid) + return + + cleanable.cleanManual() + isValid = false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt index 6eaae01c..5a8ce2d7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt @@ -2,9 +2,11 @@ package ru.dbotthepony.kstarbound.client.gl import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL +import org.lwjgl.opengl.GL14 import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.freetype.FreeType +import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException import ru.dbotthepony.kstarbound.client.gl.program.GLPrograms import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.program.GLShaderProgram @@ -12,7 +14,8 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableColorableProgra import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableProgram import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder -import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType +import ru.dbotthepony.kstarbound.client.gl.vertex.quad import ru.dbotthepony.kstarbound.client.render.Box2DRenderer import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.TileRenderers @@ -22,7 +25,10 @@ import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.Color import java.io.FileNotFoundException import java.lang.ref.Cleaner +import java.util.* import java.util.concurrent.ThreadFactory +import kotlin.collections.ArrayList +import kotlin.collections.HashMap import kotlin.reflect.KProperty private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) { @@ -47,7 +53,7 @@ private class GLStateSwitchTracker(private val enum: Int, private var value: Boo } } -private class GLStateGenericTracker(private var value: T, private val lambda: (T) -> Unit) { +private class GLStateGenericTracker(private var value: T, private val callback: (T) -> Unit) { operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): T { return value } @@ -58,12 +64,69 @@ private class GLStateGenericTracker(private var value: T, private val lambda: if (value == this.value) return - lambda.invoke(value) + callback.invoke(value) checkForGLError() this.value = value } } +@Suppress("unused") +data class BlendFunc( + val sourceColor: Func, + val destinationColor: Func, + val sourceAlpha: Func, + val destinationAlpha: Func +) { + constructor() : this( + Func.ONE, + Func.ZERO, + Func.ONE, + Func.ZERO + ) + + constructor( + source: Func, destination: Func + ) : this( + source, + destination, + source, + destination + ) + + enum class Func(val enum: Int) { + ZERO(GL_ZERO), + ONE(GL_ONE), + SRC_COLOR(GL_SRC_COLOR), + ONE_MINUS_SRC_COLOR(GL_ONE_MINUS_SRC_COLOR), + SRC_ALPHA(GL_SRC_ALPHA), + ONE_MINUS_SRC_ALPHA(GL_ONE_MINUS_SRC_ALPHA), + DST_ALPHA(GL_DST_ALPHA), + ONE_MINUS_DST_ALPHA(GL_ONE_MINUS_DST_ALPHA), + DST_COLOR(GL_DST_COLOR), + ONE_MINUS_DST_COLOR(GL_ONE_MINUS_DST_COLOR), + SRC_ALPHA_SATURATE(GL_SRC_ALPHA_SATURATE); + } + + companion object { + val DEFAULT = BlendFunc() + val ALPHA_TEST = BlendFunc(Func.SRC_ALPHA, Func.ONE_MINUS_SRC_ALPHA) + + val ONLY_ALPHA = BlendFunc( + Func.ZERO, + Func.ONE, + Func.ONE, + Func.ZERO + ) + + val ONLY_COLOR = BlendFunc( + Func.ONE, + Func.ZERO, + Func.ZERO, + Func.ONE + ) + } +} + interface GLCleanable : Cleaner.Cleanable { /** * Выставляет флаг на то, что объект был удалён вручную и вызывает clean() @@ -75,7 +138,14 @@ interface GLStreamBuilderList { val small: StreamVertexBuilder } +@Suppress("PropertyName", "unused") class GLStateTracker { + private fun isMe(state: GLStateTracker?) { + if (state != null && state != this) { + throw InvalidArgumentException("Provided object does not belong to $this state tracker (belongs to $state)") + } + } + init { check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" } TRACKERS.set(this) @@ -87,7 +157,8 @@ class GLStateTracker { GL.createCapabilities() } - private var cleanerHits = ArrayList<() -> Unit>() + private val cleanerHits = ArrayList<() -> Unit>() + private val cleaner = Cleaner.create(object : ThreadFactory { override fun newThread(r: Runnable): Thread { val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this)) @@ -101,11 +172,13 @@ class GLStateTracker { val cleanable = cleaner.register(ref) { if (!cleanManual) - LOGGER.error("{} with ID {} got leaked.", name, nativeRef) + LOGGER.error("{} with ID {} was GC'd by JVM, but it should have been removed manually.", name, nativeRef) - cleanerHits.add { - fn(nativeRef) - checkForGLError() + synchronized(cleanerHits) { + cleanerHits.add { + fn(nativeRef) + checkForGLError() + } } } @@ -120,11 +193,12 @@ class GLStateTracker { } fun cleanup() { - val copy = cleanerHits - cleanerHits = ArrayList() + synchronized(cleanerHits) { + for (lambda in cleanerHits) { + lambda.invoke() + } - for (lambda in copy) { - lambda.invoke() + cleanerHits.clear() } } @@ -135,6 +209,7 @@ class GLStateTracker { set(value) { ensureSameThread() if (field === value) return + isMe(value?.state) field = value if (value == null) { @@ -152,6 +227,7 @@ class GLStateTracker { set(value) { ensureSameThread() if (field === value) return + isMe(value?.state) field = value if (value == null) { @@ -169,6 +245,7 @@ class GLStateTracker { set(value) { ensureSameThread() if (field === value) return + isMe(value?.state) field = value if (value == null) { @@ -181,6 +258,56 @@ class GLStateTracker { checkForGLError() } + var readFramebuffer: GLFrameBuffer? = null + set(value) { + ensureSameThread() + if (field === value) return + isMe(value?.state) + field = value + + if (value == null) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) + checkForGLError() + return + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, value.pointer) + checkForGLError() + } + + var writeFramebuffer: GLFrameBuffer? = null + set(value) { + ensureSameThread() + if (field === value) return + isMe(value?.state) + field = value + + if (value == null) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) + checkForGLError() + return + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, value.pointer) + checkForGLError() + } + + var framebuffer: GLFrameBuffer? + get() { + val readFramebuffer = readFramebuffer + val writeFramebuffer = writeFramebuffer + + if (readFramebuffer == writeFramebuffer) { + return writeFramebuffer + } + + return null + } + set(value) { + readFramebuffer = value + writeFramebuffer = value + } + var program: GLShaderProgram? = null private set @@ -188,6 +315,7 @@ class GLStateTracker { set(value) { ensureSameThread() if (field === value) return + isMe(value?.state) field = value if (value == null) return glBindTexture(GL_TEXTURE_2D, value.pointer) @@ -210,6 +338,10 @@ class GLStateTracker { glClearColor(r, g, b, a) } + var blendFunc by GLStateGenericTracker(BlendFunc()) { + glBlendFuncSeparate(it.sourceColor.enum, it.destinationColor.enum, it.sourceAlpha.enum, it.destinationAlpha.enum) + } + init { glActiveTexture(GL_TEXTURE0) checkForGLError() @@ -362,42 +494,42 @@ class GLStateTracker { val flat2DLines = object : GLStreamBuilderList { override val small by lazy { - return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.LINES, 1024) + return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.LINES, 1024) } } val flat2DTriangles = object : GLStreamBuilderList { override val small by lazy { - return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.TRIANGLES, 1024) + return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.TRIANGLES, 1024) } } val flat2DQuads = object : GLStreamBuilderList { override val small by lazy { - return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS, 1024) + return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS, 1024) } } val flat2DTexturedQuads = object : GLStreamBuilderList { override val small by lazy { - return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VERTEX_TEXTURE, VertexType.QUADS, 1024) + return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS, 1024) } } val flat2DQuadLines = object : GLStreamBuilderList { override val small by lazy { - return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES, 1024) + return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES, 1024) } } val flat2DQuadWireframe = object : GLStreamBuilderList { override val small by lazy { - return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 1024) + return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME, 1024) } } val quadWireframe by lazy { - StreamVertexBuilder(this, GLAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 16384) + StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME, 16384) } val matrixStack = Matrix4fStack() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt index 81ace5ce..c946553a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt @@ -27,6 +27,7 @@ class GLTexturePropertyTracker(private val flag: Int, var value: Int) { } } +@Suppress("SameParameterValue") class GLTexture2D(val state: GLStateTracker, val name: String = "") : AutoCloseable { val pointer = glGenTextures() @@ -63,18 +64,20 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") : A private var mipsWarning = 2 - var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_LINEAR) + var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR) var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR) var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT) var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT) fun bind(): GLTexture2D { - if (mipsWarning == 1) { - LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this) - mipsWarning = 0 - } else if (mipsWarning == 2) { - mipsWarning = 1 + if (textureMinFilter != GL_LINEAR && textureMinFilter != GL_NEAREST) { + if (mipsWarning == 1) { + LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this) + mipsWarning = 0 + } else if (mipsWarning == 2) { + mipsWarning = 1 + } } state.texture2D = this @@ -101,6 +104,23 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") : A fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y) + fun allocate(mipmap: Int, loadedFormat: Int, width: Int, height: Int): GLTexture2D { + bind() + + require(width > 0) { "Invalid width $width" } + require(height > 0) { "Invalid height $height" } + this.width = width + this.height = height + glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, loadedFormat, GL_UNSIGNED_BYTE, 0L) + checkForGLError() + uploaded = true + return this + } + + fun allocate(loadedFormat: Int, width: Int, height: Int): GLTexture2D { + return allocate(0, loadedFormat, width, height) + } + private fun upload(mipmap: Int, loadedFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D { bind() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLInternalProgram.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLInternalProgram.kt index bd816843..5b90353d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLInternalProgram.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLInternalProgram.kt @@ -5,9 +5,10 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShader private fun loadShaders( vertex: Collection, - fragment: Collection + fragment: Collection, + geometry: Collection ): Array { - val result = arrayOfNulls(vertex.size + fragment.size) + val result = arrayOfNulls(vertex.size + fragment.size + geometry.size) var i = 0 for (name in vertex) { @@ -18,13 +19,18 @@ private fun loadShaders( result[i++] = GLShader.internalFragment("shaders/$name.fsh") } + for (name in geometry) { + result[i++] = GLShader.internalGeometry("shaders/$name.gsh") + } + return result as Array } open class GLInternalProgram( state: GLStateTracker, vertex: Collection, - fragment: Collection -) : GLShaderProgram(state, *loadShaders(vertex, fragment)) { + fragment: Collection, + geometry: Collection = listOf() +) : GLShaderProgram(state, *loadShaders(vertex, fragment, geometry)) { constructor(state: GLStateTracker, name: String) : this(state, listOf(name), listOf(name)) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLShaderProgram.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLShaderProgram.kt index 1d1cb4e2..3ee2fdf6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLShaderProgram.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/GLShaderProgram.kt @@ -75,7 +75,7 @@ open class GLShaderProgram(val state: GLStateTracker, vararg shaders: GLShader) glGetProgramiv(pointer, GL_LINK_STATUS, success) if (success[0] == 0) { - throw ShaderLinkException(glGetShaderInfoLog(pointer)) + throw ShaderLinkException(glGetProgramInfoLog(pointer)) } glGetError() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt index 28f6222a..71e48eea 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt @@ -1,13 +1,16 @@ package ru.dbotthepony.kstarbound.client.gl.program +import ru.dbotthepony.kstarbound.client.gl.BlendFunc import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLType import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableColorableProgram -import ru.dbotthepony.kstarbound.client.gl.shader.GLTransformableProgram import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder -import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType +import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.quad +import ru.dbotthepony.kvector.vector.Color import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -50,7 +53,130 @@ class GLLiquidProgram(state: GLStateTracker) : GLInternalProgram(state, "liquid" val transform = this["transform"]!! val builder by lazy { - StreamVertexBuilder(state, FORMAT, VertexType.QUADS, 16384) + StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384) + } + + companion object { + val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build() + } +} + +class GLLightProgram(state: GLStateTracker) : GLInternalProgram(state, "light") { + init { + link() + } + + val baselineColor = this["baselineColor"]!! + val transform = this["transform"]!! + + val builder by lazy { + StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 32) + } + + companion object { + val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build() + } +} + +class GLLightOccluderProgram(state: GLStateTracker) : GLInternalProgram(state, listOf("light_occluder"), listOf("light_occluder"), listOf("light_occluder")) { + init { + link() + } + + // val baselineColor = this["baselineColor"]!! + val transform = this["transform"]!! + val lightPosition = this["lightPosition"]!! + + val builder by lazy { + StreamVertexBuilder(state, FORMAT, GeometryType.QUADS_AS_LINES, 32) + } + + companion object { + val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build() + } +} + +class GLColorQuadProgram(state: GLStateTracker) : GLInternalProgram(state, "screen_quad") { + init { + link() + } + + val color = this["color"]!! + + private val builder by lazy { + val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1) + + builder.begin() + builder.quad(-1f, -1f, 1f, 1f) + builder.upload() + + builder + } + + fun clearAlpha() { + use() + + color.set(ALPHA) + + val old = state.blend + val oldFunc = state.blendFunc + + state.blend = true + state.blendFunc = BlendFunc.ONLY_ALPHA + builder.draw() + state.blend = old + state.blendFunc = oldFunc + } + + fun clearColor(color: Color = Color.WHITE) { + use() + + this.color.set(color) + + val old = state.blend + + state.blend = false + builder.draw() + state.blend = old + } + + companion object { + val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build() + val ALPHA = Color(0f, 0f, 0f, 1f) + } +} + +class GLTextureQuadProgram(state: GLStateTracker) : GLInternalProgram(state, "screen_quad_tex") { + init { + link() + } + + val texture = this["texture0"]!! + + private val builder by lazy { + val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1) + + builder.begin() + builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv()) + builder.upload() + + builder + } + + fun run(texture: Int = 0) { + use() + this.texture.set(texture) + builder.draw() + } + + companion object { + val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build() + } +} + +class GLFlatProgram(state: GLStateTracker, vararg shaders: GLShader) : GLTransformableColorableProgram(state, *shaders) { + val builder by lazy { + StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1024) } companion object { @@ -61,6 +187,11 @@ class GLLiquidProgram(state: GLStateTracker) : GLInternalProgram(state, "liquid" class GLPrograms(val state: GLStateTracker) { val tile by SimpleProgram("tile", ::GLTransformableColorableProgram) val font by SimpleProgram("font", ::GLTransformableColorableProgram) - val flat by SimpleProgram("flat", ::GLTransformableColorableProgram) + val flat by SimpleProgram("flat", ::GLFlatProgram) val liquid by lazy { GLLiquidProgram(state) } + val light by lazy { GLLightProgram(state) } + val lightOccluder by lazy { GLLightOccluderProgram(state) } + + val colorQuad by lazy { GLColorQuadProgram(state) } + val textureQuad by lazy { GLTextureQuadProgram(state) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt index 34b4a937..8f975d6a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt @@ -56,5 +56,6 @@ class GLShader( fun internalVertex(file: String) = GLShader(readInternal(file), GL_VERTEX_SHADER) fun internalFragment(file: String) = GLShader(readInternal(file), GL_FRAGMENT_SHADER) + fun internalGeometry(file: String) = GLShader(readInternal(file), GL_GEOMETRY_SHADER) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLUniformLocation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLUniformLocation.kt index c09795d3..6a414294 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLUniformLocation.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLUniformLocation.kt @@ -4,6 +4,7 @@ import org.lwjgl.opengl.GL41.* import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.program.GLShaderProgram import ru.dbotthepony.kvector.api.IFloatMatrix +import ru.dbotthepony.kvector.api.IStruct2f import ru.dbotthepony.kvector.api.IStruct3f import ru.dbotthepony.kvector.api.IStruct4f import java.nio.ByteBuffer @@ -19,6 +20,14 @@ class GLUniformLocation(val program: GLShaderProgram, val name: String, val poin return this } + fun set(value: IStruct2f): GLUniformLocation { + program.state.ensureSameThread() + val (v0, v1) = value + glProgramUniform2f(program.pointer, pointer, v0, v1) + checkForGLError() + return this + } + fun set(value: IStruct3f): GLUniformLocation { program.state.ensureSameThread() val (v0, v1, v2) = value diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt index 16223224..5dec8a9a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt @@ -26,9 +26,9 @@ private fun put(type: Int, memory: OutputStream, value: Int) { * Класс для построения геометрии для загрузки в память видеокарты. */ @Suppress("unchecked_cast") -abstract class AbstractVertexBuilder>( +abstract class AbstractVertexBuilder>( val attributes: GLAttributeList, - val type: VertexType, + val type: GeometryType, ) { protected abstract val vertexMemory: OutputStream protected abstract val elementMemory: OutputStream @@ -165,97 +165,98 @@ abstract class AbstractVertexBuilder>( attributeIndex++ return this as T } - - // Помощники - fun quad( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): T { - check(type.elements == 4) { "Currently building $type" } - - lambda(vertex().pushVec2f(x0, y0), 0).end() - lambda(vertex().pushVec2f(x1, y0), 1).end() - lambda(vertex().pushVec2f(x0, y1), 2).end() - lambda(vertex().pushVec2f(x1, y1), 3).end() - - return this as T - } - - fun quadRotated( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - x: Float, - y: Float, - angle: Double, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): T { - check(type.elements == 4) { "Currently building $type" } - - val s = sin(angle).toFloat() - val c = cos(angle).toFloat() - - lambda(vertex().pushVec2f(x + x0 * c - s * y0, y + s * x0 + c * y0), 0).end() - lambda(vertex().pushVec2f(x + x1 * c - s * y0, y + s * x1 + c * y0), 1).end() - lambda(vertex().pushVec2f(x + x0 * c - s * y1, y + s * x0 + c * y1), 2).end() - lambda(vertex().pushVec2f(x + x1 * c - s * y1, y + s * x1 + c * y1), 3).end() - - return this as T - } - - fun quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): T { - return quad( - aabb.mins.x.toFloat(), - aabb.mins.y.toFloat(), - aabb.maxs.x.toFloat(), - aabb.maxs.y.toFloat(), - lambda - ) - } - - fun quadZ( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - z: Float, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): T { - check(type.elements == 4) { "Currently building $type" } - - 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 as T - } - - fun quadRotatedZ( - x0: Float, - y0: Float, - x1: Float, - y1: Float, - z: Float, - x: Float, - y: Float, - angle: Double, - lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM - ): T { - check(type.elements == 4) { "Currently building $type" } - - val s = sin(angle).toFloat() - val c = cos(angle).toFloat() - - 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() - - return this as T - } +} + + +// Помощники +fun > T.quad( + x0: Float, + y0: Float, + x1: Float, + y1: Float, + lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM +): T { + check(type.elements == 4) { "Currently building $type" } + + lambda(vertex().pushVec2f(x0, y0), 0).end() + lambda(vertex().pushVec2f(x1, y0), 1).end() + lambda(vertex().pushVec2f(x0, y1), 2).end() + lambda(vertex().pushVec2f(x1, y1), 3).end() + + return this +} + +fun > T.quadRotated( + x0: Float, + y0: Float, + x1: Float, + y1: Float, + x: Float, + y: Float, + angle: Double, + lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM +): T { + check(type.elements == 4) { "Currently building $type" } + + val s = sin(angle).toFloat() + val c = cos(angle).toFloat() + + lambda(vertex().pushVec2f(x + x0 * c - s * y0, y + s * x0 + c * y0), 0).end() + lambda(vertex().pushVec2f(x + x1 * c - s * y0, y + s * x1 + c * y0), 1).end() + lambda(vertex().pushVec2f(x + x0 * c - s * y1, y + s * x0 + c * y1), 2).end() + lambda(vertex().pushVec2f(x + x1 * c - s * y1, y + s * x1 + c * y1), 3).end() + + return this +} + +fun > T.quad(aabb: AABB, lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): T { + return quad( + aabb.mins.x.toFloat(), + aabb.mins.y.toFloat(), + aabb.maxs.x.toFloat(), + aabb.maxs.y.toFloat(), + lambda + ) +} + +fun > T.quadZ( + x0: Float, + y0: Float, + x1: Float, + y1: Float, + z: Float, + lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM +): T { + check(type.elements == 4) { "Currently building $type" } + + 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 > T.quadRotatedZ( + x0: Float, + y0: Float, + x1: Float, + y1: Float, + z: Float, + x: Float, + y: Float, + angle: Double, + lambda: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM +): T { + check(type.elements == 4) { "Currently building $type" } + + val s = sin(angle).toFloat() + val c = cos(angle).toFloat() + + 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() + + return this } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt index 913335dc..affbbf99 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt @@ -6,24 +6,24 @@ import ru.dbotthepony.kstarbound.util.ByteBufferOutputStream open class DirectVertexBuilder>( attributes: GLAttributeList, - type: VertexType, + type: GeometryType, val maxElements: Int, ) : AbstractVertexBuilder>(attributes, type) { val maxIndexCount = maxElements * type.indicies.size val maxVertexCount = maxElements * type.elements - override val vertexMemory = ByteBufferOutputStream.directLE(maxVertexCount) - override val elementMemory = ByteBufferOutputStream.directLE(maxIndexCount) - - override val elementIndexType: Int = when (maxIndexCount) { + final override val elementIndexType: Int = when (maxIndexCount) { // api performance issue 102: glDrawElements uses element index type 'GL_UNSIGNED_BYTE' that is not optimal for the current hardware configuration; consider using 'GL_UNSIGNED_SHORT' instead // in 0 .. 255 -> GL_UNSIGNED_BYTE in 0 .. 65535 -> GL46.GL_UNSIGNED_SHORT else -> GL46.GL_UNSIGNED_INT } + final override val vertexMemory = ByteBufferOutputStream.directLE(maxVertexCount * attributes.stride) + final override val elementMemory = ByteBufferOutputStream.directLE(maxIndexCount * (if (elementIndexType == GL46.GL_UNSIGNED_SHORT) 2 else 4)) + override fun ensureIndexCapacity() { - if (vertexCount >= maxVertexCount) { + if (vertexCount > maxVertexCount) { throw IndexOutOfBoundsException("Vertex count overflow (can hold max of $maxElements elements, that's $maxVertexCount vertexes)") } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt similarity index 80% rename from src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexType.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt index 252d75ac..e6a41669 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/VertexType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt @@ -1,9 +1,9 @@ package ru.dbotthepony.kstarbound.client.gl.vertex -enum class VertexType(val elements: Int, val indicies: IntArray) { +enum class GeometryType(val elements: Int, val indicies: IntArray) { LINES(2, intArrayOf(0, 1)), TRIANGLES(3, intArrayOf(0, 1, 2)), QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)), QUADS_AS_LINES(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3)), QUADS_AS_LINES_WIREFRAME(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)), -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt index ad44f588..15a11f71 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt @@ -17,7 +17,7 @@ import java.nio.ByteBuffer */ class HeapVertexBuilder( attributes: GLAttributeList, - type: VertexType, + type: GeometryType, ) : AbstractVertexBuilder(attributes, type) { override val vertexMemory = FastByteArrayOutputStream() override val elementMemory = FastByteArrayOutputStream() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt index 9a98664e..136ae88e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt @@ -11,7 +11,7 @@ import java.io.Closeable class StreamVertexBuilder( val state: GLStateTracker, attributes: GLAttributeList, - type: VertexType, + type: GeometryType, maxElements: Int, ) : DirectVertexBuilder(attributes, type, maxElements), Closeable { private val vao = state.newVAO() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/EntityRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/EntityRenderer.kt index fa2808d2..ab428a46 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/EntityRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/EntityRenderer.kt @@ -5,6 +5,7 @@ import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.ClientChunk import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.quadRotatedZ import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile import ru.dbotthepony.kvector.matrix.Matrix4fStack diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt index fe51b6c2..10a38b1f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt @@ -9,7 +9,8 @@ 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.HeapVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers -import ru.dbotthepony.kstarbound.client.gl.vertex.VertexType +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType +import ru.dbotthepony.kstarbound.client.gl.vertex.quad import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.vector.Color @@ -307,7 +308,7 @@ class Font( ebo.bind() vbo.bind() - val builder = HeapVertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, VertexType.QUADS) + val builder = HeapVertexBuilder(GLAttributeList.VERTEX_2D_TEXTURE, GeometryType.QUADS) builder.quad(0f, 0f, width, height, QuadTransformers.uv()) builder.upload(vbo, ebo, GL_STATIC_DRAW) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index 863300a6..92cc459e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -168,7 +168,7 @@ private enum class TileRenderTesselateResult { HALT } -private fun vertexTextureBuilder() = HeapVertexBuilder(GLAttributeList.TILE, VertexType.QUADS) +private fun vertexTextureBuilder() = HeapVertexBuilder(GLAttributeList.TILE, GeometryType.QUADS) private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester { override fun test(thisTile: ITileState, otherTile: ITileState): Boolean { diff --git a/src/main/resources/shaders/font.fsh b/src/main/resources/shaders/font.fsh index f33f27a5..96dc5cfe 100644 --- a/src/main/resources/shaders/font.fsh +++ b/src/main/resources/shaders/font.fsh @@ -8,6 +8,5 @@ uniform sampler2D _texture; uniform vec4 _color; void main() { - float sampled = texture(_texture, _uv_out).r; - _color_out = vec4(_color.x * sampled, _color.y * sampled, _color.z * sampled, _color.w * sampled); + _color_out = _color * texture(_texture, _uv_out).r; } diff --git a/src/main/resources/shaders/light.fsh b/src/main/resources/shaders/light.fsh new file mode 100644 index 00000000..329b33e8 --- /dev/null +++ b/src/main/resources/shaders/light.fsh @@ -0,0 +1,11 @@ + +#version 460 + +in vec2 oUVCoords; +out vec4 resultColor; + +uniform vec4 baselineColor; + +void main() { + resultColor = vec4(baselineColor) * smoothstep(0.0, 1.0, 1.0 - sqrt(pow(oUVCoords.x - 0.5, 2.0) + pow(oUVCoords.y - 0.5, 2.0)) * 2.0); +} diff --git a/src/main/resources/shaders/light.vsh b/src/main/resources/shaders/light.vsh new file mode 100644 index 00000000..a7d5c597 --- /dev/null +++ b/src/main/resources/shaders/light.vsh @@ -0,0 +1,14 @@ + +#version 460 + +layout (location = 0) in vec2 vertexPos; +layout (location = 1) in vec2 uvCoords; + +out vec2 oUVCoords; + +uniform mat4 transform; + +void main() { + gl_Position = transform * vec4(vertexPos, 0.0, 1.0); + oUVCoords = uvCoords; +} diff --git a/src/main/resources/shaders/light_occluder.fsh b/src/main/resources/shaders/light_occluder.fsh new file mode 100644 index 00000000..78164c03 --- /dev/null +++ b/src/main/resources/shaders/light_occluder.fsh @@ -0,0 +1,10 @@ + +#version 460 + +uniform vec2 lightPosition; + +out vec4 resultColor; + +void main() { + resultColor = vec4(0.0, 0.0, 0.0, 0.0); +} diff --git a/src/main/resources/shaders/light_occluder.gsh b/src/main/resources/shaders/light_occluder.gsh new file mode 100644 index 00000000..580c421b --- /dev/null +++ b/src/main/resources/shaders/light_occluder.gsh @@ -0,0 +1,36 @@ + +#version 460 + +layout (lines) in; + +layout (triangle_strip, max_vertices = 5) out; + +uniform mat4 transform; +uniform vec2 lightPosition; + +in vec2 originalPos[]; + +void main() { + vec4 a = gl_in[0].gl_Position; + vec4 b = gl_in[1].gl_Position; + + vec2 aInv = originalPos[0]; + vec2 bInv = originalPos[1]; + + gl_Position = b; + EmitVertex(); + + gl_Position = a; + EmitVertex(); + + gl_Position = transform * vec4(aInv + (aInv - lightPosition) * 10000, 0, 1); + EmitVertex(); + + gl_Position = transform * vec4(bInv + (bInv - lightPosition) * 10000, 0, 1); + EmitVertex(); + + gl_Position = b; + EmitVertex(); + + EndPrimitive(); +} diff --git a/src/main/resources/shaders/light_occluder.vsh b/src/main/resources/shaders/light_occluder.vsh new file mode 100644 index 00000000..c63e6291 --- /dev/null +++ b/src/main/resources/shaders/light_occluder.vsh @@ -0,0 +1,13 @@ + +#version 460 + +layout (location = 0) in vec2 vertexPos; + +uniform mat4 transform; + +out vec2 originalPos; + +void main() { + gl_Position = transform * vec4(vertexPos, 0.0, 1.0); + originalPos = vertexPos; +} diff --git a/src/main/resources/shaders/screen_quad.fsh b/src/main/resources/shaders/screen_quad.fsh new file mode 100644 index 00000000..fdc26879 --- /dev/null +++ b/src/main/resources/shaders/screen_quad.fsh @@ -0,0 +1,10 @@ + +#version 460 + +out vec4 resultColor; + +uniform vec4 color; + +void main() { + resultColor = color; +} diff --git a/src/main/resources/shaders/screen_quad.vsh b/src/main/resources/shaders/screen_quad.vsh new file mode 100644 index 00000000..23540e7e --- /dev/null +++ b/src/main/resources/shaders/screen_quad.vsh @@ -0,0 +1,8 @@ + +#version 460 + +layout (location = 0) in vec2 vertexPos; + +void main() { + gl_Position = vec4(vertexPos, 0.0, 1.0); +} diff --git a/src/main/resources/shaders/screen_quad_tex.fsh b/src/main/resources/shaders/screen_quad_tex.fsh new file mode 100644 index 00000000..dba4b580 --- /dev/null +++ b/src/main/resources/shaders/screen_quad_tex.fsh @@ -0,0 +1,11 @@ + +#version 460 + +out vec4 resultColor; +in vec2 uvPos; + +uniform sampler2D texture0; + +void main() { + resultColor = texture(texture0, uvPos); +} diff --git a/src/main/resources/shaders/screen_quad_tex.vsh b/src/main/resources/shaders/screen_quad_tex.vsh new file mode 100644 index 00000000..8778a315 --- /dev/null +++ b/src/main/resources/shaders/screen_quad_tex.vsh @@ -0,0 +1,12 @@ + +#version 460 + +layout (location = 0) in vec2 vertexPos; +layout (location = 1) in vec2 inUVPos; + +out vec2 uvPos; + +void main() { + gl_Position = vec4(vertexPos, 0.0, 1.0); + uvPos = inUVPos; +}