From 3ad0e78c1008f7158c794a5ca9567e2a5aaa5255 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 17 Sep 2023 21:25:20 +0700 Subject: [PATCH] Remove unused graphics stuff, merge GLStateTracker with StarboundClient --- build.gradle.kts | 2 +- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 16 +- .../kstarbound/client/ClientSettings.kt | 2 - .../kstarbound/client/StarboundClient.kt | 752 +++++++++++++++--- .../kstarbound/client/gl/GLFrameBuffer.kt | 35 +- .../kstarbound/client/gl/GLStateTracker.kt | 640 --------------- .../kstarbound/client/gl/GLTexture.kt | 26 +- .../kstarbound/client/gl/ScissorRect.kt | 2 +- .../kstarbound/client/gl/VertexArrayObject.kt | 14 +- .../client/gl/VertexBufferObject.kt | 27 +- .../gl/properties/GLStateFuncTracker.kt | 23 + .../gl/properties/GLStateGenericTracker.kt | 23 + .../client/gl/properties/GLStateIntTracker.kt | 26 + .../gl/properties/GLStateSwitchTracker.kt | 29 + .../client/gl/properties/TexturesTracker.kt | 38 + .../kstarbound/client/gl/shader/GLShader.kt | 41 + .../client/gl/shader/GLShaderProgram.kt | 31 +- .../kstarbound/client/gl/shader/Programs.kt | 174 +--- .../client/gl/vertex/StreamVertexBuilder.kt | 12 +- .../kstarbound/client/render/Box2DRenderer.kt | 5 +- .../kstarbound/client/render/Camera.kt | 12 - .../kstarbound/client/render/Font.kt | 3 +- .../kstarbound/client/render/Mesh.kt | 12 +- .../kstarbound/client/render/RenderConfig.kt | 4 +- .../kstarbound/client/render/TileRenderer.kt | 12 +- .../client/render/entity/EntityRenderer.kt | 8 - .../client/render/entity/ItemRenderer.kt | 8 +- .../kstarbound/client/world/ClientChunk.kt | 3 - .../kstarbound/client/world/ClientWorld.kt | 15 +- .../dbotthepony/kstarbound/defs/Drawable.kt | 19 +- .../io/json/builder/BuilderAdapter.kt | 15 +- .../kstarbound/util/DoubleEdgeProgression.kt | 25 - .../kstarbound/util/INotNullDelegate.kt | 5 - .../util/IndexedArraySpliterator.kt | 48 -- .../util/NotNullTwoDimensionalArray.kt | 69 -- .../dbotthepony/kstarbound/util/NotNullVar.kt | 26 - .../kstarbound/util/TwoDimensionalArray.kt | 61 -- src/main/resources/shaders/screen_quad.fsh | 10 - src/main/resources/shaders/screen_quad.vsh | 8 - .../resources/shaders/screen_quad_tex.fsh | 11 - .../resources/shaders/screen_quad_tex.vsh | 12 - 41 files changed, 957 insertions(+), 1347 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateFuncTracker.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateGenericTracker.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateIntTracker.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateSwitchTracker.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/TexturesTracker.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/INotNullDelegate.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/IndexedArraySpliterator.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullTwoDimensionalArray.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullVar.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt delete mode 100644 src/main/resources/shaders/screen_quad.fsh delete mode 100644 src/main/resources/shaders/screen_quad.vsh delete mode 100644 src/main/resources/shaders/screen_quad_tex.fsh delete mode 100644 src/main/resources/shaders/screen_quad_tex.vsh diff --git a/build.gradle.kts b/build.gradle.kts index 0e7a184f..bc921dd7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -82,7 +82,7 @@ dependencies { implementation("com.github.jnr:jnr-ffi:2.2.13") implementation("ru.dbotthepony:kbox2d:2.4.1.6") - implementation("ru.dbotthepony:kvector:2.5.0") + implementation("ru.dbotthepony:kvector:2.6.0") implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 58bce657..d365e562 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -157,10 +157,10 @@ fun main() { //client.camera.pos = Vector2f(0f, 0f) 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("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) - client.gl.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f) + client.font.render("${ent.position}", y = 100f, scale = 0.25f) + client.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) + client.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) + client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f) } client.onPreDrawWorld { @@ -219,10 +219,10 @@ fun main() { lightRenderer.renderOutputAdditive() }*/ - client.gl.box2dRenderer.drawShapes = false - client.gl.box2dRenderer.drawPairs = false - client.gl.box2dRenderer.drawAABB = false - client.gl.box2dRenderer.drawJoints = false + client.box2dRenderer.drawShapes = false + client.box2dRenderer.drawPairs = false + client.box2dRenderer.drawAABB = false + client.box2dRenderer.drawJoints = false //ent.spawn() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt index c4bcbeaa..aa6f245b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientSettings.kt @@ -7,6 +7,4 @@ data class ClientSettings( * Масштаб в единицу означает что один Starbound Unit будет равен 8 пикселям на экране */ var zoom: Float = 2f, - - var debugCollisions: Boolean = false, ) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 1a8dcee7..bfc076c4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -1,34 +1,62 @@ package ru.dbotthepony.kstarbound.client +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine import org.apache.logging.log4j.LogManager import org.lwjgl.BufferUtils import org.lwjgl.glfw.Callbacks import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFWErrorCallback +import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL46.* +import org.lwjgl.opengl.GLCapabilities import org.lwjgl.system.MemoryStack 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.freetype.FreeType +import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException import ru.dbotthepony.kstarbound.client.gl.BlendFunc -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer +import ru.dbotthepony.kstarbound.client.gl.GLTexture2D +import ru.dbotthepony.kstarbound.client.gl.ScissorRect +import ru.dbotthepony.kstarbound.client.gl.VBOType +import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject +import ru.dbotthepony.kstarbound.client.gl.VertexBufferObject +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import ru.dbotthepony.kstarbound.client.gl.properties.GLStateFuncTracker +import ru.dbotthepony.kstarbound.client.gl.properties.GLStateGenericTracker +import ru.dbotthepony.kstarbound.client.gl.properties.GLStateIntTracker +import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker +import ru.dbotthepony.kstarbound.client.gl.properties.TexturesTracker +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.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 +import ru.dbotthepony.kstarbound.client.render.Box2DRenderer import ru.dbotthepony.kstarbound.client.render.Camera +import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.TextAlignY import ru.dbotthepony.kstarbound.client.render.TileRenderers 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.JVMTimeSource -import ru.dbotthepony.kstarbound.util.PausableTimeSource 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.util2d.AABB import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2d @@ -36,17 +64,45 @@ 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.nio.ByteBuffer import java.nio.ByteOrder +import java.time.Duration +import java.util.* import java.util.concurrent.locks.LockSupport +import java.util.concurrent.locks.ReentrantLock import kotlin.collections.ArrayList +import kotlin.math.roundToInt class StarboundClient : Closeable { - val time = PausableTimeSource(JVMTimeSource.INSTANCE) val window: Long val camera = Camera(this) val input = UserInput() - val gl: GLStateTracker + val thread: Thread = Thread.currentThread() + val capabilities: GLCapabilities + + var viewportX: Int = 0 + private set + var viewportY: Int = 0 + private set + var viewportWidth: Int = 0 + private set + var viewportHeight: Int = 0 + private set + + var viewportCellX = 0 + private set + var viewportCellY = 0 + private set + var viewportCellWidth = 0 + private set + var viewportCellHeight = 0 + private set + var viewportRectangle = AABB.rectangle(Vector2d.ZERO, 0.0, 0.0) + private set + + var fullbright = true var gameTerminated = false private set @@ -65,9 +121,519 @@ class StarboundClient : Closeable { private set get() = Matrix4f.unmodifiable(field) + var isRenderingGame = true + private set + + private val scissorStack = LinkedList() + private val cleanerBacklog = ArrayList<() -> Unit>() + private val onDrawGUI = ArrayList<() -> Unit>() + private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>() + private val onPostDrawWorld = ArrayList<() -> Unit>() + private val onPostDrawWorldOnce = ArrayList<(LayeredRenderer) -> Unit>() + private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>() + private val terminateCallbacks = ArrayList<() -> Unit>() private val startupTextList = ArrayList() private var finishStartupRendering = System.currentTimeMillis() + 4000L + private val cleaner = Cleaner.create { r -> + val thread = Thread(r, "OpenGL Cleaner for '${thread.name}'") + thread.priority = 2 + thread + } + + @Volatile + var objectsCleaned = 0L + private set + + @Volatile + var gcHits = 0L + private set + + init { + check(CLIENTS.get() == null) { "Already has OpenGL context existing at ${Thread.currentThread()}!" } + CLIENTS.set(this) + + lock.lock() + + try { + if (!glfwInitialized) { + check(GLFW.glfwInit()) { "Unable to initialize GLFW" } + glfwInitialized = true + + GLFWErrorCallback.create { error, description -> + LOGGER.error("LWJGL error {}: {}", error, description) + }.set() + } + } finally { + lock.unlock() + } + + GLFW.glfwDefaultWindowHints() + + GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE) + GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE) + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4) + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 6) + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE) + + window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) + require(window != MemoryUtil.NULL) { "Unable to create GLFW window" } + startupTextList.add("Created GLFW window") + + input.installCallback(window) + + GLFW.glfwMakeContextCurrent(window) + + // This line is critical for LWJGL's interoperation with GLFW's + // OpenGL context, or any context that is managed externally. + // LWJGL detects the context that is current in the current thread, + // creates the GLCapabilities instance and makes the OpenGL + // bindings available for use. + capabilities = GL.createCapabilities() + + GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h -> + if (w == 0 || h == 0) { + isRenderingGame = false + } else { + isRenderingGame = true + setViewport(0, 0, w, h) + viewportMatrixScreen = updateViewportMatrixScreen() + viewportMatrixWorld = updateViewportMatrixWorld() + + for (callback in onViewportChanged) { + callback.invoke(w, h) + } + } + } + + val stack = MemoryStack.stackPush() + + try { + val pWidth = stack.mallocInt(1) + val pHeight = stack.mallocInt(1) + + GLFW.glfwGetWindowSize(window, pWidth, pHeight) + + val vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!! + + GLFW.glfwSetWindowPos( + window, + (vidmode.width() - pWidth[0]) / 2, + (vidmode.height() - pHeight[0]) / 2 + ) + + setViewport(0, 0, pWidth[0], pHeight[0]) + viewportMatrixScreen = updateViewportMatrixScreen() + viewportMatrixWorld = updateViewportMatrixWorld() + } finally { + stack.close() + } + + // vsync + GLFW.glfwSwapInterval(0) + + GLFW.glfwShowWindow(window) + putDebugLog("Initialized GLFW window") + } + + val programs = GLPrograms() + + 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) } + + // минимальное время хранения 5 минут и... + private val named2DTextures0: Cache = Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .build() + + // ...бесконечное хранение пока кто-то все ещё использует текстуру + private val named2DTextures1: Cache = Caffeine.newBuilder() + .weakValues() + .build() + + private val missingTexture: GLTexture2D by lazy { + newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also { + it.textureMinFilter = GL_NEAREST + it.textureMagFilter = GL_NEAREST + } + } + + private val missingTexturePath = "/assetmissing.png" + + val matrixStack = Matrix4fStack() + val freeType = FreeType() + val font = Font() + val box2dRenderer = Box2DRenderer() + + fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable { + val cleanable = cleaner.register(ref) { + objectsCleaned++ + + if (isSameThread()) { + fn(nativeRef) + checkForGLError() + } else { + gcHits++ + + synchronized(cleanerBacklog) { + cleanerBacklog.add { + fn(nativeRef) + checkForGLError() + } + } + } + } + + return cleanable + } + + fun cleanup() { + synchronized(cleanerBacklog) { + for (lambda in cleanerBacklog) { + lambda.invoke() + } + + cleanerBacklog.clear() + } + } + + var blend by GLStateSwitchTracker(GL_BLEND) + var scissor by GLStateSwitchTracker(GL_SCISSOR_TEST) + + var cull by GLStateSwitchTracker(GL_CULL_FACE) + var cullMode by GLStateFuncTracker(::glCullFace, GL_BACK) + + var textureUnpackAlignment by GLStateIntTracker(::glPixelStorei, GL_UNPACK_ALIGNMENT, 4) + + var scissorRect by GLStateGenericTracker(ScissorRect(0, 0, 0, 0)) { + // require(it.x >= 0) { "Invalid X ${it.x}"} + // require(it.y >= 0) { "Invalid Y ${it.y}"} + + require(it.width >= 0) { "Invalid width ${it.width}"} + require(it.height >= 0) { "Invalid height ${it.height}"} + + glScissor(it.x, it.y, it.width, it.height) + } + + var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST) + + var VBO: VertexBufferObject? = null + set(value) { + ensureSameThread() + + if (field !== value) { + isMe(value?.client) + require(value?.isArray != false) { "Provided buffer object is not of Array type" } + glBindBuffer(GL_ARRAY_BUFFER, value?.pointer ?: 0) + checkForGLError("Setting Vertex Buffer Object") + field = value + } + } + + var EBO: VertexBufferObject? = null + set(value) { + ensureSameThread() + + if (field !== value) { + isMe(value?.client) + require(value?.isElementArray != false) { "Provided buffer object is not of Array type" } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, value?.pointer ?: 0) + checkForGLError("Setting Element Buffer Object") + field = value + } + } + + var VAO: VertexArrayObject? = null + set(value) { + ensureSameThread() + + if (field !== value) { + isMe(value?.client) + glBindVertexArray(value?.pointer ?: 0) + checkForGLError("Setting Vertex Array Object") + field = value + } + } + + var readFramebuffer: GLFrameBuffer? = null + set(value) { + ensureSameThread() + if (field === value) return + isMe(value?.client) + 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?.client) + 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 + set(value) { + ensureSameThread() + + if (value !== field) { + isMe(value?.client) + glUseProgram(value?.pointer ?: 0) + checkForGLError("Setting shader program") + field = value + } + } + + var activeTexture = 0 + set(value) { + ensureSameThread() + + if (field != value) { + require(value >= 0) { "Invalid texture block $value" } + require(value < 80) { "Too big texture block index $value, OpenGL 4.6 guarantee only 80!" } + glActiveTexture(GL_TEXTURE0 + value) + checkForGLError() + field = value + } + } + + var texture2D: GLTexture2D? by TexturesTracker(80) + + var clearColor by GLStateGenericTracker(RGBAColor.WHITE) { + val (r, g, b, a) = it + 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() + } + + fun setViewport(x: Int, y: Int, width: Int, height: Int) { + ensureSameThread() + + if (viewportX != x || viewportY != y || viewportWidth != width || viewportHeight != height) { + glViewport(x, y, width, height) + checkForGLError("Setting viewport") + viewportX = x + viewportY = y + viewportWidth = width + viewportHeight = height + } + } + + fun pushScissorRect(x: Float, y: Float, width: Float, height: Float) { + return pushScissorRect(x.roundToInt(), y.roundToInt(), width.roundToInt(), height.roundToInt()) + } + + @Suppress("NAME_SHADOWING") + fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) { + var x = x + var y = y + var width = width + var height = height + + val peek = scissorStack.lastOrNull() + + if (peek != null) { + x = x.coerceAtLeast(peek.x) + y = y.coerceAtLeast(peek.y) + width = width.coerceAtMost(peek.width) + height = height.coerceAtMost(peek.height) + + if (peek.x == x && peek.y == y && peek.width == width && peek.height == height) { + scissorStack.add(peek) + return + } + } + + val rect = ScissorRect(x, y, width, height) + scissorStack.add(rect) + scissorRect = rect + scissor = true + } + + fun popScissorRect() { + scissorStack.removeLast() + + val peek = scissorStack.lastOrNull() + + if (peek == null) { + scissor = false + return + } + + val y = viewportHeight - peek.y - peek.height + scissorRect = ScissorRect(peek.x, y, peek.width, peek.height) + } + + val currentScissorRect get() = scissorStack.lastOrNull() + + fun ensureSameThread() { + if (thread !== Thread.currentThread()) { + throw IllegalAccessException("Trying to access $this outside of $thread!") + } + } + + fun isSameThread() = thread === Thread.currentThread() + fun newTexture(name: String = "") = GLTexture2D(name) + + fun loadTexture(path: String): GLTexture2D { + ensureSameThread() + + return named2DTextures0.get(path) { + named2DTextures1.get(it) { + val data = Image.get(it) + + if (data == null) { + LOGGER.error("Texture {} is missing! Falling back to {}", it, missingTexturePath) + missingTexture + } else { + newTexture(it).upload(data).also { + it.textureMinFilter = GL_NEAREST + it.textureMagFilter = GL_NEAREST + } + } + } + } + } + + fun bind(obj: VertexBufferObject): VertexBufferObject { + if (obj.type == VBOType.ARRAY) + VBO = obj + else + EBO = obj + + return obj + } + + fun unbind(obj: VertexBufferObject): VertexBufferObject { + if (obj.type == VBOType.ARRAY) + if (obj == VBO) + VBO = null + else + if (obj == EBO) + EBO = null + + return obj + } + + fun bind(obj: VertexArrayObject): VertexArrayObject { + VAO = obj + return obj + } + + fun unbind(obj: VertexArrayObject): VertexArrayObject { + if (obj == VAO) + VAO = null + + return obj + } + + fun newVBO() = VertexBufferObject.vbo() + fun newEBO() = VertexBufferObject.ebo() + fun newVAO() = VertexArrayObject() + + inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) { + val builder = quadWireframe + + builder.builder.begin() + lambda.invoke(builder.builder) + builder.upload() + + programs.flat.use() + programs.flat.color = color + programs.flat.transform = matrixStack.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) + + fun vertex(contents: String) = GLShader(contents, GL_VERTEX_SHADER) + fun fragment(contents: String) = GLShader(contents, GL_FRAGMENT_SHADER) + + 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) + fun putDebugLog(text: String, replace: Boolean = false) { if (replace) { if (startupTextList.isEmpty()) { @@ -82,6 +648,12 @@ class StarboundClient : Closeable { finishStartupRendering = System.currentTimeMillis() + 4000L } + private fun isMe(state: StarboundClient?) { + if (state != null && state != this) { + throw InvalidArgumentException("Provided object does not belong to $this state tracker (belongs to $state)") + } + } + private fun updateViewportMatrixScreen(): Matrix4f { return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f).translate(Vector3f(z = 2f)) } @@ -157,96 +729,21 @@ class StarboundClient : Closeable { return Vector2f(relativeX, relativeY) } - var isRenderingGame = true - private set - - init { - GLFWErrorCallback.create { error, description -> - LOGGER.error("LWJGL error {}: {}", error, description) - }.set() - - check(GLFW.glfwInit()) { "Unable to initialize GLFW" } - - GLFW.glfwDefaultWindowHints() - - GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE) - GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE) - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4) - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 6) - GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE) - - window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) - require(window != MemoryUtil.NULL) { "Unable to create GLFW window" } - startupTextList.add("Created GLFW window") - - input.installCallback(window) - - GLFW.glfwMakeContextCurrent(window) - gl = GLStateTracker(this) - - GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h -> - if (w == 0 || h == 0) { - isRenderingGame = false - } else { - isRenderingGame = true - gl.setViewport(0, 0, w, h) - viewportMatrixScreen = updateViewportMatrixScreen() - viewportMatrixWorld = updateViewportMatrixWorld() - - for (callback in onViewportChanged) { - callback.invoke(w, h) - } - } - } - - val stack = MemoryStack.stackPush() - - try { - val pWidth = stack.mallocInt(1) - val pHeight = stack.mallocInt(1) - - GLFW.glfwGetWindowSize(window, pWidth, pHeight) - - val vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!! - - GLFW.glfwSetWindowPos( - window, - (vidmode.width() - pWidth[0]) / 2, - (vidmode.height() - pHeight[0]) / 2 - ) - - gl.setViewport(0, 0, pWidth[0], pHeight[0]) - viewportMatrixScreen = updateViewportMatrixScreen() - viewportMatrixWorld = updateViewportMatrixWorld() - } finally { - stack.close() - } - - // vsync - GLFW.glfwSwapInterval(0) - - GLFW.glfwShowWindow(window) - putDebugLog("Initialized GLFW window") - } - - val viewportWidth by gl::viewportWidth - val viewportHeight by gl::viewportHeight - val tileRenderers = TileRenderers(this) - var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true) init { putDebugLog("Initialized OpenGL context") - gl.clearColor = RGBAColor.SLATE_GRAY + clearColor = RGBAColor.SLATE_GRAY - gl.blend = true - gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA + blend = true + blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA } var frameRenderTime = 0.0 private set + private var lastRender = JVMTimeSource.INSTANCE.seconds val framesPerSecond get() = if (frameRenderTime == 0.0) 1.0 else 1.0 / frameRenderTime private val frameRenderTimes = DoubleArray(60) { 1.0 } @@ -267,17 +764,6 @@ class StarboundClient : Closeable { val settings = ClientSettings() - var viewportCellX = 0 - private set - var viewportCellY = 0 - private set - var viewportCellWidth = 0 - private set - var viewportCellHeight = 0 - private set - var viewportRectangle = AABB.rectangle(Vector2d.ZERO, 0.0, 0.0) - private set - val viewportCells: ICellAccess = object : ICellAccess { override fun getCell(x: Int, y: Int): IChunkCell? { return world?.getCell(x + viewportCellX, y + viewportCellY) @@ -291,11 +777,9 @@ class StarboundClient : Closeable { var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight) private set - val viewportLightingTexture = gl.newTexture("Viewport Lighting") + val viewportLightingTexture = newTexture("Viewport Lighting") private var viewportLightingMem: ByteBuffer? = null - var fullbright = true - fun updateViewportParams() { viewportRectangle = AABB.rectangle( camera.pos.toDoubleVector(), @@ -319,12 +803,6 @@ class StarboundClient : Closeable { } } - private val onDrawGUI = ArrayList<() -> Unit>() - private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>() - private val onPostDrawWorld = ArrayList<() -> Unit>() - private val onPostDrawWorldOnce = ArrayList<(LayeredRenderer) -> Unit>() - private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>() - fun onViewportChanged(callback: (width: Int, height: Int) -> Unit) { onViewportChanged.add(callback) } @@ -345,10 +823,8 @@ class StarboundClient : Closeable { onPostDrawWorldOnce.add(lambda) } - private var lastRender = JVMTimeSource.INSTANCE.seconds - fun renderFrame(): Boolean { - gl.ensureSameThread() + ensureSameThread() val diff = JVMTimeSource.INSTANCE.seconds - lastRender @@ -367,7 +843,7 @@ class StarboundClient : Closeable { val world = world if (!isRenderingGame) { - gl.cleanup() + cleanup() GLFW.glfwPollEvents() if (world != null) { @@ -385,11 +861,11 @@ class StarboundClient : Closeable { if (Starbound.initialized) world.think() - gl.clearColor = RGBAColor.SLATE_GRAY + clearColor = RGBAColor.SLATE_GRAY glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) - gl.matrixStack.clear(viewportMatrixWorld) + matrixStack.clear(viewportMatrixWorld) - gl.matrixStack.push().last() + matrixStack.push().last() .translateWithMultiplication(viewportWidth / 2f, viewportHeight / 2f, 2f) // центр экрана + координаты отрисовки мира .scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера .translateWithMultiplication(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере @@ -409,7 +885,7 @@ class StarboundClient : Closeable { layers = layers, size = viewportRectangle) - layers.render(gl.matrixStack) + layers.render(matrixStack) val viewportLightingMem = viewportLightingMem @@ -420,8 +896,8 @@ class StarboundClient : Closeable { viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096)) viewportLightingMem.position(0) - val old = gl.textureUnpackAlignment - gl.textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1 + val old = textureUnpackAlignment + textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1 viewportLightingTexture.upload( GL_RGB, @@ -432,16 +908,16 @@ class StarboundClient : Closeable { viewportLightingMem ) - gl.textureUnpackAlignment = old + textureUnpackAlignment = old viewportLightingTexture.textureMinFilter = GL_LINEAR //viewportLightingTexture.textureMagFilter = GL_NEAREST //viewportLightingTexture.generateMips() - gl.blendFunc = BlendFunc.MULTIPLY_BY_SRC + blendFunc = BlendFunc.MULTIPLY_BY_SRC - gl.quadTexture(viewportLightingTexture) { + quadTexture(viewportLightingTexture) { it.quad( (viewportCellX).toFloat(), (viewportCellY).toFloat(), @@ -451,7 +927,7 @@ class StarboundClient : Closeable { ) } - gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA + blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA } world.physics.debugDraw() @@ -460,10 +936,10 @@ class StarboundClient : Closeable { lambda.invoke() } - gl.matrixStack.pop() + matrixStack.pop() } - gl.matrixStack.clear(viewportMatrixScreen) + matrixStack.clear(viewportMatrixScreen) val thisTime = System.currentTimeMillis() @@ -474,20 +950,20 @@ class StarboundClient : Closeable { alpha = (finishStartupRendering - thisTime) / 1000f } - gl.matrixStack.push() - gl.matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat()) + matrixStack.push() + matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat()) var shade = 255 for (i in startupTextList.size - 1 downTo 0) { - val size = gl.font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) - gl.matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f) + 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) if (shade > 120) { shade -= 10 } } - gl.matrixStack.pop() + matrixStack.pop() } for (fn in onDrawGUI) { @@ -496,8 +972,8 @@ class StarboundClient : Closeable { val runtime = Runtime.getRuntime() - gl.font.render("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f) - gl.font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = gl.font.lineHeight * 0.5f, scale = 0.4f) + font.render("FPS: ${(averageFramesPerSecond * 100f).toInt() / 100f}", scale = 0.4f) + font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 0.5f, scale = 0.4f) GLFW.glfwSwapBuffers(window) GLFW.glfwPollEvents() @@ -505,13 +981,11 @@ class StarboundClient : Closeable { camera.think(Starbound.TICK_TIME_ADVANCE) - gl.cleanup() + cleanup() return true } - private val terminateCallbacks = ArrayList<() -> Unit>() - fun onTermination(lambda: () -> Unit) { terminateCallbacks.add(lambda) } @@ -537,5 +1011,25 @@ class StarboundClient : Closeable { companion object { private val LOGGER = LogManager.getLogger(StarboundClient::class.java) + private val CLIENTS = ThreadLocal() + + @JvmStatic + fun current() = checkNotNull(CLIENTS.get()) { "No client registered to current thread (${Thread.currentThread()})" } + @JvmStatic + fun currentOrNull(): StarboundClient? = CLIENTS.get() + + private val lock = ReentrantLock() + @Volatile + private var glfwInitialized = false + + @JvmStatic + fun readInternal(file: String): String { + return ClassLoader.getSystemClassLoader().getResourceAsStream(file)!!.bufferedReader() + .let { + val read = it.readText() + it.close() + read + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt index 9546e827..cbc4c599 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLFrameBuffer.kt @@ -5,28 +5,23 @@ 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 +import ru.dbotthepony.kstarbound.client.StarboundClient -class GLFrameBuffer(val state: GLStateTracker) { - init { - state.ensureSameThread() - } - +class GLFrameBuffer { + val client = StarboundClient.current() val pointer = GL46.glGenFramebuffers() init { checkForGLError("Creating framebuffer") - state.registerCleanable(this, GL46::glDeleteFramebuffers, pointer) + client.registerCleanable(this, GL46::glDeleteFramebuffers, pointer) } val isComplete: Boolean get() { - state.ensureSameThread() + client.ensureSameThread() return glCheckNamedFramebufferStatus(pointer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE } @@ -38,16 +33,16 @@ class GLFrameBuffer(val state: GLStateTracker) { throw IllegalStateException("Already has texture attached") } - val texture = GLTexture2D(state, "framebuffer_$pointer") + val texture = GLTexture2D("framebuffer_$pointer") texture.allocate(format, width, height) - val old = state.framebuffer + val old = client.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 + client.framebuffer = old } fun reattachTexture(width: Int, height: Int, format: Int = GL_RGB) { @@ -56,24 +51,24 @@ class GLFrameBuffer(val state: GLStateTracker) { } fun bind() { - state.framebuffer = this + client.framebuffer = this } fun bindWrite() { - state.writeFramebuffer = this + client.writeFramebuffer = this } fun bindRead() { - state.readFramebuffer = this + client.readFramebuffer = this } fun unbind() { - if (state.writeFramebuffer == this) { - state.writeFramebuffer = null + if (client.writeFramebuffer == this) { + client.writeFramebuffer = null } - if (state.readFramebuffer == this) { - state.readFramebuffer = null + if (client.readFramebuffer == this) { + client.readFramebuffer = null } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt deleted file mode 100644 index a2fea6ef..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ /dev/null @@ -1,640 +0,0 @@ -package ru.dbotthepony.kstarbound.client.gl - -import com.github.benmanes.caffeine.cache.Cache -import com.github.benmanes.caffeine.cache.Caffeine -import org.apache.logging.log4j.LogManager -import org.lwjgl.opengl.GL -import org.lwjgl.opengl.GL46.* -import org.lwjgl.opengl.GLCapabilities -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.client.StarboundClient -import ru.dbotthepony.kstarbound.client.freetype.FreeType -import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException -import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms -import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram -import ru.dbotthepony.kstarbound.client.gl.shader.ShaderCompilationException -import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList -import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder -import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType -import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder -import ru.dbotthepony.kstarbound.client.render.Box2DRenderer -import ru.dbotthepony.kstarbound.client.render.Font -import ru.dbotthepony.kstarbound.defs.image.Image -import ru.dbotthepony.kvector.api.IStruct4f -import ru.dbotthepony.kvector.arrays.Matrix4fStack -import ru.dbotthepony.kvector.util2d.AABB -import ru.dbotthepony.kvector.vector.RGBAColor -import java.io.File -import java.lang.ref.Cleaner -import java.time.Duration -import java.util.* -import java.util.function.IntConsumer -import kotlin.collections.ArrayList -import kotlin.math.roundToInt -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) { - operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Boolean { - return value - } - - operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Boolean) { - glStateTracker.ensureSameThread() - - if (value == this.value) - return - - if (value) { - glEnable(enum) - } else { - glDisable(enum) - } - - checkForGLError() - this.value = value - } -} - -private class GLStateIntTracker(private val fn: Function, private val enum: Int, private var value: Int) { - fun interface Function { - fun invoke(enum: Int, value: Int) - } - - operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int { - return value - } - - operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Int) { - glStateTracker.ensureSameThread() - - if (value == this.value) - return - - fn.invoke(enum, value) - checkForGLError() - this.value = value - } -} - -private class GLStateFuncTracker(private val glFunc: IntConsumer, private var value: Int) { - operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int { - return value - } - - operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Int) { - glStateTracker.ensureSameThread() - - if (value == this.value) - return - - glFunc.accept(value) - checkForGLError() - this.value = value - } -} - -private class GLStateGenericTracker(private var value: T, private val callback: (T) -> Unit) : ReadWriteProperty { - override fun getValue(thisRef: GLStateTracker, property: KProperty<*>): T { - return value - } - - override fun setValue(thisRef: GLStateTracker, property: KProperty<*>, value: T) { - thisRef.ensureSameThread() - - if (value == this.value) - return - - callback.invoke(value) - checkForGLError() - this.value = value - } -} - -private class TexturesTracker(maxValue: Int) : ReadWriteProperty { - private val values = arrayOfNulls(maxValue) - - override fun getValue(thisRef: GLStateTracker, property: KProperty<*>): GLTexture2D? { - return values[thisRef.activeTexture] - } - - override fun setValue(thisRef: GLStateTracker, property: KProperty<*>, value: GLTexture2D?) { - thisRef.ensureSameThread() - - require(value == null || thisRef === value.state) { "$value does not belong to $thisRef" } - - if (values[thisRef.activeTexture] === value) { - return - } - - values[thisRef.activeTexture] = value - - if (value == null) { - glBindTexture(GL_TEXTURE_2D, 0) - checkForGLError() - return - } - - glBindTexture(GL_TEXTURE_2D, value.pointer) - checkForGLError() - } -} - -@Suppress("PropertyName", "unused") -class GLStateTracker(val client: StarboundClient) { - 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) - } - - // This line is critical for LWJGL's interoperation with GLFW's - // OpenGL context, or any context that is managed externally. - // LWJGL detects the context that is current in the current thread, - // creates the GLCapabilities instance and makes the OpenGL - // bindings available for use. - val capabilities: GLCapabilities = GL.createCapabilities() - - val programs = GLPrograms(this) - - val flat2DLines by lazy { StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.LINES) } - val flat2DTriangles by lazy { StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.TRIANGLES) } - val flat2DTexturedQuads by lazy { StreamVertexBuilder(this, GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS) } - val quadWireframe by lazy { StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME) } - - val matrixStack = Matrix4fStack() - val freeType = FreeType() - val font = Font(this) - val thread: Thread = Thread.currentThread() - val box2dRenderer = Box2DRenderer(this) - - private val scissorStack = LinkedList() - private val cleanerBacklog = ArrayList<() -> Unit>() - - @Volatile - var objectsCleaned = 0L - private set - - @Volatile - var gcHits = 0L - private set - - private val cleaner = Cleaner.create { r -> - val thread = Thread(r, "OpenGL Cleaner for '${thread.name}'") - thread.priority = 2 - thread - } - - fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable { - val cleanable = cleaner.register(ref) { - objectsCleaned++ - - if (isSameThread()) { - fn(nativeRef) - checkForGLError() - } else { - gcHits++ - - synchronized(cleanerBacklog) { - cleanerBacklog.add { - fn(nativeRef) - checkForGLError() - } - } - } - } - - return cleanable - } - - fun cleanup() { - synchronized(cleanerBacklog) { - for (lambda in cleanerBacklog) { - lambda.invoke() - } - - cleanerBacklog.clear() - } - } - - var blend by GLStateSwitchTracker(GL_BLEND) - var scissor by GLStateSwitchTracker(GL_SCISSOR_TEST) - - var cull by GLStateSwitchTracker(GL_CULL_FACE) - var cullMode by GLStateFuncTracker(::glCullFace, GL_BACK) - - var textureUnpackAlignment by GLStateIntTracker(::glPixelStorei, GL_UNPACK_ALIGNMENT, 4) - - var scissorRect by GLStateGenericTracker(ScissorRect(0, 0, 0, 0)) { - // require(it.x >= 0) { "Invalid X ${it.x}"} - // require(it.y >= 0) { "Invalid Y ${it.y}"} - - require(it.width >= 0) { "Invalid width ${it.width}"} - require(it.height >= 0) { "Invalid height ${it.height}"} - - glScissor(it.x, it.y, it.width, it.height) - } - - var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST) - - var VBO: VertexBufferObject? = null - set(value) { - ensureSameThread() - - if (field !== value) { - isMe(value?.state) - require(value?.isArray != false) { "Provided buffer object is not of Array type" } - glBindBuffer(GL_ARRAY_BUFFER, value?.pointer ?: 0) - checkForGLError("Setting Vertex Buffer Object") - field = value - } - } - - var EBO: VertexBufferObject? = null - set(value) { - ensureSameThread() - - if (field !== value) { - isMe(value?.state) - require(value?.isElementArray != false) { "Provided buffer object is not of Array type" } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, value?.pointer ?: 0) - checkForGLError("Setting Element Buffer Object") - field = value - } - } - - var VAO: VertexArrayObject? = null - set(value) { - ensureSameThread() - - if (field !== value) { - isMe(value?.state) - glBindVertexArray(value?.pointer ?: 0) - checkForGLError("Setting Vertex Array Object") - field = value - } - } - - 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 - set(value) { - ensureSameThread() - - if (value !== field) { - isMe(value?.state) - glUseProgram(value?.pointer ?: 0) - checkForGLError("Setting shader program") - field = value - } - } - - var activeTexture = 0 - set(value) { - ensureSameThread() - - if (field != value) { - require(value >= 0) { "Invalid texture block $value" } - require(value < 80) { "Too big texture block index $value, OpenGL 4.6 guarantee only 80!" } - glActiveTexture(GL_TEXTURE0 + value) - checkForGLError() - field = value - } - } - - var texture2D: GLTexture2D? by TexturesTracker(80) - - var clearColor by GLStateGenericTracker(RGBAColor.WHITE) { - val (r, g, b, a) = it - 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() - } - - var viewportX: Int = 0 - private set - var viewportY: Int = 0 - private set - var viewportWidth: Int = 0 - private set - var viewportHeight: Int = 0 - private set - - fun setViewport(x: Int, y: Int, width: Int, height: Int) { - ensureSameThread() - - if (viewportX != x || viewportY != y || viewportWidth != width || viewportHeight != height) { - glViewport(x, y, width, height) - checkForGLError("Setting viewport") - viewportX = x - viewportY = y - viewportWidth = width - viewportHeight = height - } - } - - fun pushScissorRect(x: Float, y: Float, width: Float, height: Float) { - return pushScissorRect(x.roundToInt(), y.roundToInt(), width.roundToInt(), height.roundToInt()) - } - - @Suppress("NAME_SHADOWING") - fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) { - var x = x - var y = y - var width = width - var height = height - - val peek = scissorStack.lastOrNull() - - if (peek != null) { - x = x.coerceAtLeast(peek.x) - y = y.coerceAtLeast(peek.y) - width = width.coerceAtMost(peek.width) - height = height.coerceAtMost(peek.height) - - if (peek.x == x && peek.y == y && peek.width == width && peek.height == height) { - scissorStack.add(peek) - return - } - } - - val rect = ScissorRect(x, y, width, height) - scissorStack.add(rect) - scissorRect = rect - scissor = true - } - - fun popScissorRect() { - scissorStack.removeLast() - - val peek = scissorStack.lastOrNull() - - if (peek == null) { - scissor = false - return - } - - val y = viewportHeight - peek.y - peek.height - scissorRect = ScissorRect(peek.x, y, peek.width, peek.height) - } - - val currentScissorRect get() = scissorStack.lastOrNull() - - fun ensureSameThread() { - if (thread !== Thread.currentThread()) { - throw IllegalAccessException("Trying to access $this outside of $thread!") - } - } - - fun isSameThread() = thread === Thread.currentThread() - - fun newVBO(type: VBOType = VBOType.ARRAY): VertexBufferObject { - return VertexBufferObject(this, type) - } - - fun newEBO() = newVBO(VBOType.ELEMENT_ARRAY) - fun newVAO() = VertexArrayObject(this) - fun newTexture(name: String = "") = GLTexture2D(this, name) - - // минимальное время хранения 5 минут и... - private val named2DTextures0: Cache = Caffeine.newBuilder() - .expireAfterAccess(Duration.ofMinutes(5)) - .build() - - // ...бесконечное хранение пока кто-то все ещё использует текстуру - private val named2DTextures1: Cache = Caffeine.newBuilder() - .weakValues() - .build() - - private val missingTexture: GLTexture2D by lazy { - newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also { - it.textureMinFilter = GL_NEAREST - it.textureMagFilter = GL_NEAREST - } - } - - private val missingTexturePath = "/assetmissing.png" - - fun loadTexture(path: String): GLTexture2D { - ensureSameThread() - - return named2DTextures0.get(path) { - named2DTextures1.get(it) { - val data = Image.get(it) - - if (data == null) { - LOGGER.error("Texture {} is missing! Falling back to {}", it, missingTexturePath) - missingTexture - } else { - newTexture(it).upload(data).also { - it.textureMinFilter = GL_NEAREST - it.textureMagFilter = GL_NEAREST - } - } - } - } - } - - fun bind(obj: VertexBufferObject): VertexBufferObject { - if (obj.type == VBOType.ARRAY) - VBO = obj - else - EBO = obj - - return obj - } - - fun unbind(obj: VertexBufferObject): VertexBufferObject { - if (obj.type == VBOType.ARRAY) - if (obj == VBO) - VBO = null - else - if (obj == EBO) - EBO = null - - return obj - } - - fun bind(obj: VertexArrayObject): VertexArrayObject { - VAO = obj - return obj - } - - fun unbind(obj: VertexArrayObject): VertexArrayObject { - if (obj == VAO) - VAO = null - - return obj - } - - inline fun quadWireframe(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) { - val builder = quadWireframe - - builder.builder.begin() - lambda.invoke(builder.builder) - builder.upload() - - programs.flat.use() - programs.flat.color = color - programs.flat.transform = matrixStack.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) - } - } - - inner class Shader(body: String, type: Int) { - constructor(body: File, type: Int) : this(body.also { require(it.exists()) { "Shader file does not exists: $body" } }.readText(), type) - - init { - ensureSameThread() - } - - val pointer = glCreateShader(type) - - init { - checkForGLError() - registerCleanable(this, ::glDeleteShader, pointer) - } - - init { - if (body == "") { - throw IllegalArgumentException("Shader source is empty") - } - - glShaderSource(pointer, body) - glCompileShader(pointer) - - val result = intArrayOf(0) - glGetShaderiv(pointer, GL_COMPILE_STATUS, result) - - if (result[0] == 0) { - throw ShaderCompilationException(glGetShaderInfoLog(pointer)) - } - - checkForGLError() - } - } - - fun vertex(file: File) = Shader(file, GL_VERTEX_SHADER) - fun fragment(file: File) = Shader(file, GL_FRAGMENT_SHADER) - - fun vertex(contents: String) = Shader(contents, GL_VERTEX_SHADER) - fun fragment(contents: String) = Shader(contents, GL_FRAGMENT_SHADER) - - fun internalVertex(file: String) = Shader(readInternal(file), GL_VERTEX_SHADER) - fun internalFragment(file: String) = Shader(readInternal(file), GL_FRAGMENT_SHADER) - fun internalGeometry(file: String) = Shader(readInternal(file), GL_GEOMETRY_SHADER) - - companion object { - private val LOGGER = LogManager.getLogger(GLStateTracker::class.java) - private val TRACKERS = ThreadLocal() - - fun current() = checkNotNull(TRACKERS.get()) { "Current thread has no OpenGL State attached" } - fun currentOrNull(): GLStateTracker? = TRACKERS.get() - - private fun readInternal(file: String): String { - return ClassLoader.getSystemClassLoader().getResourceAsStream(file)!!.bufferedReader() - .let { - val read = it.readText() - it.close() - read - } - } - } -} 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 41799fe5..ca91cafa 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client.gl import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL46.* import org.lwjgl.stb.STBImage +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kvector.vector.Vector2i import java.io.File @@ -20,7 +21,7 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value: } operator fun setValue(thisRef: GLTexture2D, property: KProperty<*>, value: Int) { - thisRef.state.ensureSameThread() + thisRef.client.ensureSameThread() if (this.value == value) return this.value = value glTextureParameteri(thisRef.pointer, flag, value) @@ -29,16 +30,13 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value: } @Suppress("SameParameterValue") -class GLTexture2D(val state: GLStateTracker, val name: String = "") { - init { - state.ensureSameThread() - } - +class GLTexture2D(val name: String = "") { + val client = StarboundClient.current() val pointer = glGenTextures() init { checkForGLError() - state.registerCleanable(this, ::glDeleteTextures, pointer) + client.registerCleanable(this, ::glDeleteTextures, pointer) } var width = 0 @@ -74,12 +72,12 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") { var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT) fun bind(): GLTexture2D { - state.texture2D = this + client.texture2D = this return this } fun generateMips(): GLTexture2D { - state.ensureSameThread() + client.ensureSameThread() glGenerateTextureMipmap(pointer) checkForGLError("Generating texture mipmaps") return this @@ -150,7 +148,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") { } fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D { - state.ensureSameThread() + client.ensureSameThread() if (!path.exists()) { throw FileNotFoundException("${path.absolutePath} does not exist") @@ -176,7 +174,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") { } fun upload(path: File): GLTexture2D { - state.ensureSameThread() + client.ensureSameThread() if (!path.exists()) { throw FileNotFoundException("${path.absolutePath} does not exist") @@ -210,7 +208,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") { } fun upload(buff: ByteBuffer, memoryFormat: Int, bufferFormat: Int): GLTexture2D { - state.ensureSameThread() + client.ensureSameThread() val getwidth = intArrayOf(0) val getheight = intArrayOf(0) @@ -228,7 +226,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") { } fun upload(buff: ByteBuffer): GLTexture2D { - state.ensureSameThread() + client.ensureSameThread() val getwidth = intArrayOf(0) val getheight = intArrayOf(0) @@ -254,7 +252,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") { } fun upload(data: Image): GLTexture2D { - state.ensureSameThread() + client.ensureSameThread() val bufferFormat = when (val numChannels = data.amountOfChannels) { 1 -> GL_R diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/ScissorRect.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/ScissorRect.kt index 4de4f86b..9a34e2d8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/ScissorRect.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/ScissorRect.kt @@ -16,4 +16,4 @@ data class ScissorRect(val x: Int, val y: Int, val width: Int, val height: Int) this.x + this.width, this.y + this.height, ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt index 5545454c..503491e8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexArrayObject.kt @@ -1,32 +1,34 @@ package ru.dbotthepony.kstarbound.client.gl import org.lwjgl.opengl.GL46.* +import ru.dbotthepony.kstarbound.client.StarboundClient -class VertexArrayObject(val state: GLStateTracker) { +class VertexArrayObject { + val client = StarboundClient.current() val pointer = glGenVertexArrays() init { checkForGLError() - state.registerCleanable(this, ::glDeleteVertexArrays, pointer) + client.registerCleanable(this, ::glDeleteVertexArrays, pointer) } fun bind(): VertexArrayObject { - return state.bind(this) + return client.bind(this) } fun unbind(): VertexArrayObject { - return state.unbind(this) + return client.unbind(this) } fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): VertexArrayObject { - state.ensureSameThread() + client.ensureSameThread() glVertexAttribPointer(position, size, type, normalize, stride, offset) checkForGLError() return this } fun enableAttribute(position: Int): VertexArrayObject { - state.ensureSameThread() + client.ensureSameThread() glEnableVertexArrayAttrib(pointer, position) //glEnableVertexAttribArray(position) checkForGLError() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexBufferObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexBufferObject.kt index baa28bc5..d3c6d699 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexBufferObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/VertexBufferObject.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client.gl import org.lwjgl.opengl.GL46.* import org.lwjgl.system.MemoryUtil +import ru.dbotthepony.kstarbound.client.StarboundClient import java.nio.ByteBuffer enum class VBOType(val glType: Int) { @@ -9,36 +10,37 @@ enum class VBOType(val glType: Int) { ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER), } -class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) { +class VertexBufferObject private constructor(val type: VBOType) { + val client = StarboundClient.current() val pointer = glGenBuffers() init { checkForGLError("Creating Vertex Buffer Object") - state.registerCleanable(this, ::glDeleteBuffers, pointer) + client.registerCleanable(this, ::glDeleteBuffers, pointer) } val isArray get() = type == VBOType.ARRAY val isElementArray get() = type == VBOType.ELEMENT_ARRAY fun bind(): VertexBufferObject { - state.bind(this) + client.bind(this) return this } fun unbind(): VertexBufferObject { - state.unbind(this) + client.unbind(this) return this } fun bufferData(data: ByteBuffer, usage: Int): VertexBufferObject { - state.ensureSameThread() + client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } fun bufferData(data: ByteBuffer, usage: Int, length: Long): VertexBufferObject { - state.ensureSameThread() + client.ensureSameThread() if (length > data.remaining().toLong()) { throw IndexOutOfBoundsException("Tried to upload $data into $pointer with offset at ${data.position()} with length of $length, but that is longer than remaining data length of ${data.remaining()}!") @@ -51,30 +53,35 @@ class VertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType. } fun bufferData(data: IntArray, usage: Int): VertexBufferObject { - state.ensureSameThread() + client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } fun bufferData(data: FloatArray, usage: Int): VertexBufferObject { - state.ensureSameThread() + client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } fun bufferData(data: DoubleArray, usage: Int): VertexBufferObject { - state.ensureSameThread() + client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } fun bufferData(data: LongArray, usage: Int): VertexBufferObject { - state.ensureSameThread() + client.ensureSameThread() glNamedBufferData(pointer, data, usage) checkForGLError() return this } + + companion object { + fun vbo() = VertexBufferObject(VBOType.ARRAY) + fun ebo() = VertexBufferObject(VBOType.ELEMENT_ARRAY) + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateFuncTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateFuncTracker.kt new file mode 100644 index 00000000..f81c6cd4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateFuncTracker.kt @@ -0,0 +1,23 @@ +package ru.dbotthepony.kstarbound.client.gl.properties + +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import java.util.function.IntConsumer +import kotlin.reflect.KProperty + +class GLStateFuncTracker(private val glFunc: IntConsumer, private var value: Int) { + operator fun getValue(glStateTracker: StarboundClient, property: KProperty<*>): Int { + return value + } + + operator fun setValue(glStateTracker: StarboundClient, property: KProperty<*>, value: Int) { + glStateTracker.ensureSameThread() + + if (value == this.value) + return + + glFunc.accept(value) + checkForGLError() + this.value = value + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateGenericTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateGenericTracker.kt new file mode 100644 index 00000000..a4af4efa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateGenericTracker.kt @@ -0,0 +1,23 @@ +package ru.dbotthepony.kstarbound.client.gl.properties + +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +class GLStateGenericTracker(private var value: T, private val callback: (T) -> Unit) : ReadWriteProperty { + override fun getValue(thisRef: StarboundClient, property: KProperty<*>): T { + return value + } + + override fun setValue(thisRef: StarboundClient, property: KProperty<*>, value: T) { + thisRef.ensureSameThread() + + if (value == this.value) + return + + callback.invoke(value) + checkForGLError() + this.value = value + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateIntTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateIntTracker.kt new file mode 100644 index 00000000..c32ac418 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateIntTracker.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.kstarbound.client.gl.properties + +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import kotlin.reflect.KProperty + +class GLStateIntTracker(private val fn: Function, private val enum: Int, private var value: Int) { + fun interface Function { + fun invoke(enum: Int, value: Int) + } + + operator fun getValue(glStateTracker: StarboundClient, property: KProperty<*>): Int { + return value + } + + operator fun setValue(glStateTracker: StarboundClient, property: KProperty<*>, value: Int) { + glStateTracker.ensureSameThread() + + if (value == this.value) + return + + fn.invoke(enum, value) + checkForGLError() + this.value = value + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateSwitchTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateSwitchTracker.kt new file mode 100644 index 00000000..fb6dcf45 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/GLStateSwitchTracker.kt @@ -0,0 +1,29 @@ +package ru.dbotthepony.kstarbound.client.gl.properties + +import org.lwjgl.opengl.GL11.glDisable +import org.lwjgl.opengl.GL11.glEnable +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import kotlin.reflect.KProperty + +class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) { + operator fun getValue(glStateTracker: StarboundClient, property: KProperty<*>): Boolean { + return value + } + + operator fun setValue(glStateTracker: StarboundClient, property: KProperty<*>, value: Boolean) { + glStateTracker.ensureSameThread() + + if (value == this.value) + return + + if (value) { + glEnable(enum) + } else { + glDisable(enum) + } + + checkForGLError() + this.value = value + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/TexturesTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/TexturesTracker.kt new file mode 100644 index 00000000..60f9e7af --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/properties/TexturesTracker.kt @@ -0,0 +1,38 @@ +package ru.dbotthepony.kstarbound.client.gl.properties + +import org.lwjgl.opengl.GL11.GL_TEXTURE_2D +import org.lwjgl.opengl.GL11.glBindTexture +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.GLTexture2D +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +class TexturesTracker(maxValue: Int) : ReadWriteProperty { + private val values = arrayOfNulls(maxValue) + + override fun getValue(thisRef: StarboundClient, property: KProperty<*>): GLTexture2D? { + return values[thisRef.activeTexture] + } + + override fun setValue(thisRef: StarboundClient, property: KProperty<*>, value: GLTexture2D?) { + thisRef.ensureSameThread() + + require(value == null || thisRef === value.client) { "$value does not belong to $thisRef" } + + if (values[thisRef.activeTexture] === value) { + return + } + + values[thisRef.activeTexture] = value + + if (value == null) { + glBindTexture(GL_TEXTURE_2D, 0) + checkForGLError() + return + } + + glBindTexture(GL_TEXTURE_2D, value.pointer) + checkForGLError() + } +} 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 new file mode 100644 index 00000000..af6458cb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShader.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.kstarbound.client.gl.shader + +import org.lwjgl.opengl.GL20.GL_COMPILE_STATUS +import org.lwjgl.opengl.GL20.glCompileShader +import org.lwjgl.opengl.GL20.glCreateShader +import org.lwjgl.opengl.GL20.glDeleteShader +import org.lwjgl.opengl.GL20.glGetShaderInfoLog +import org.lwjgl.opengl.GL20.glGetShaderiv +import org.lwjgl.opengl.GL20.glShaderSource +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import java.io.File + +class GLShader(body: String, type: Int) { + constructor(body: File, type: Int) : this(body.also { require(it.exists()) { "Shader file does not exists: $body" } }.readText(), type) + val client = StarboundClient.current() + val pointer = glCreateShader(type) + + init { + checkForGLError() + client.registerCleanable(this, ::glDeleteShader, pointer) + } + + init { + if (body == "") { + throw IllegalArgumentException("Shader source is empty") + } + + glShaderSource(pointer, body) + glCompileShader(pointer) + + val result = intArrayOf(0) + glGetShaderiv(pointer, GL_COMPILE_STATUS, result) + + if (result[0] == 0) { + throw ShaderCompilationException(glGetShaderInfoLog(pointer)) + } + + checkForGLError() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt index 07a97a06..f78e288a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/GLShaderProgram.kt @@ -5,7 +5,7 @@ import it.unimi.dsi.fastutil.objects.Object2BooleanFunction import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import org.lwjgl.opengl.GL46.* -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kvector.api.IStruct2f @@ -24,19 +24,16 @@ import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty open class GLShaderProgram( - val state: GLStateTracker, - shaders: Iterable, + shaders: Iterable, val attributes: GLAttributeList ) { - init { - state.ensureSameThread() - } + val client = StarboundClient.current() val pointer = glCreateProgram() init { checkForGLError("Creating shader program") - state.registerCleanable(this, ::glDeleteProgram, pointer) + client.registerCleanable(this, ::glDeleteProgram, pointer) for (shader in shaders) { glAttachShader(pointer, shader.pointer) @@ -56,7 +53,7 @@ open class GLShaderProgram( } fun use(): GLShaderProgram { - state.program = this + client.program = this return this } @@ -64,7 +61,7 @@ open class GLShaderProgram( private val uniformsExist = Object2BooleanArrayMap() fun isUniformPresent(name: String): Boolean { - state.ensureSameThread() + client.ensureSameThread() return uniformsExist.computeIfAbsent(name, Object2BooleanFunction { glGetUniformLocation(pointer, name) != -1 }) } @@ -78,7 +75,7 @@ open class GLShaderProgram( abstract inner class Uniform(val name: String) : ReadWriteProperty { init { - state.ensureSameThread() + client.ensureSameThread() require(!locations.containsKey(name)) { "Already has uniform $name for ${this@GLShaderProgram} (${locations[name]})" } } @@ -110,7 +107,7 @@ open class GLShaderProgram( inner class FUniform(name: String) : Uniform(name) { override var value: Float = 0f set(value) { - state.ensureSameThread() + client.ensureSameThread() if (field != value) { glProgramUniform1f(pointer, location, value) @@ -135,7 +132,7 @@ open class GLShaderProgram( override var value: IStruct2f = Vector2f.ZERO set(value) { - state.ensureSameThread() + client.ensureSameThread() val (v0, v1) = value @@ -157,7 +154,7 @@ open class GLShaderProgram( override var value: IStruct3f = Vector3f.ZERO set(value) { - state.ensureSameThread() + client.ensureSameThread() val (v0, v1, v2) = value @@ -179,7 +176,7 @@ open class GLShaderProgram( inner class F3x3Uniform(name: String) : Uniform(name) { override var value: Matrix3f = Matrix3f.zero() set(value) { - state.ensureSameThread() + client.ensureSameThread() if (field != value) { buff3x3.position(0) @@ -198,7 +195,7 @@ open class GLShaderProgram( override var value: Matrix4f = Matrix4f.zero() set(value) { - state.ensureSameThread() + client.ensureSameThread() buff4x4.position(0) value.storeRowColumn(buff4x4) @@ -223,7 +220,7 @@ open class GLShaderProgram( override var value: IStruct4f = Vector4f.ZERO set(value) { - state.ensureSameThread() + client.ensureSameThread() val (v0, v1, v2, v3) = value @@ -243,7 +240,7 @@ open class GLShaderProgram( inner class IUniform(name: String) : Uniform(name) { override var value: Int = 0 set(value) { - state.ensureSameThread() + client.ensureSameThread() if (field != value) { glProgramUniform1i(pointer, location, value) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt index bd0593da..a72619af 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt @@ -1,33 +1,27 @@ package ru.dbotthepony.kstarbound.client.gl.shader -import ru.dbotthepony.kstarbound.client.gl.BlendFunc -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.GLType import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType -import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.vector.RGBAColor -private fun GLStateTracker.shaders(name: String): List { - return listOf(internalVertex("shaders/$name.vsh"), internalFragment("shaders/$name.fsh")) +private fun internalVertex(string: String) = StarboundClient.current().internalVertex(string) +private fun internalFragment(string: String) = StarboundClient.current().internalFragment(string) + +private fun shaders(name: String): List { + val client = StarboundClient.current() + return listOf(client.internalVertex("shaders/$name.vsh"), client.internalFragment("shaders/$name.fsh")) } -private fun GLStateTracker.gshaders(name: String): List { - return listOf( - internalVertex(name), - internalFragment(name), - internalGeometry(name) - ) -} - -class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("liquid"), FORMAT) { +class GLLiquidProgram : GLShaderProgram(shaders("liquid"), FORMAT) { var baselineColor by F4Uniform("baselineColor") var transform by F4x4Uniform("transform") val builder by lazy { - StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384) + StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384) } companion object { @@ -35,118 +29,11 @@ class GLLiquidProgram(state: GLStateTracker) : GLShaderProgram(state, state.shad } } -class GLLightProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("light"), FORMAT) { - var baselineColor by F4Uniform("baselineColor") +class GLFlatColorProgram : GLShaderProgram(shaders("flat_color"), FORMAT) { var transform by F4x4Uniform("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 GLColorQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad"), FORMAT) { - var color by F4Uniform("color") - - private val builder by lazy { - val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1) - - builder.builder.begin() - builder.builder.quad(-1f, -1f, 1f, 1f) - builder.upload() - - builder - } - - fun clearAlpha() { - use() - - color = 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: RGBAColor = RGBAColor.WHITE) { - use() - - this.color = 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 = RGBAColor(0f, 0f, 0f, 1f) - } -} - -class GLTextureQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex"), FORMAT) { - var texture by IUniform("texture0") - - private val builder by lazy { - val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1) - - builder.builder.begin() - builder.builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv()) - builder.upload() - - builder - } - - fun run(texture: Int = 0) { - use() - this.texture = texture - builder.draw() - } - - companion object { - val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build() - } -} - -class GLTextureBlurredQuadProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("screen_quad_tex_blur"), FORMAT) { - var texture by IUniform("texture0") - - private val builder by lazy { - val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1) - - builder.builder.begin() - builder.builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv()) - builder.upload() - - builder - } - - fun run(texture: Int = 0) { - use() - this.texture = texture - builder.draw() - } - - companion object { - val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build() - } -} - -class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat_color"), FORMAT) { - var transform by F4x4Uniform("transform") - - val builder by lazy { - StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384) + StreamVertexBuilder(FORMAT, GeometryType.QUADS, 16384) } companion object { @@ -154,7 +41,7 @@ class GLFlatColorProgram(state: GLStateTracker) : GLShaderProgram(state, state.s } } -class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("tile"), FORMAT) { +class GLTileProgram : GLShaderProgram(shaders("tile"), FORMAT) { var transform by F4x4Uniform("transform") var color by F4Uniform("color") var texture by IUniform("texture0") @@ -169,7 +56,7 @@ class GLTileProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader } } -class GLFontProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) { +class GLFontProgram : GLShaderProgram(shaders("font"), GLAttributeList.VERTEX_2D_TEXTURE) { var transform by F4x4Uniform("transform") var color by F4Uniform("color") var texture by IUniform("texture0") @@ -180,7 +67,7 @@ class GLFontProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader } } -class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shaders("flat"), GLAttributeList.VEC2F) { +class GLFlatProgram : GLShaderProgram(shaders("flat"), GLAttributeList.VEC2F) { var transform by F4x4Uniform("transform") var color by F4Uniform("color") @@ -189,12 +76,12 @@ class GLFlatProgram(state: GLStateTracker) : GLShaderProgram(state, state.shader } } -class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) { +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(state, GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) + StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC3F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) } init { @@ -202,12 +89,12 @@ class GLTexturedProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(s } } -class GLTextured2dProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/2dtexture.glsl"), state.internalFragment("shaders/fragment/texture.glsl")), GLAttributeList.VERTEX_TEXTURE) { +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(state, GLAttributeList.Builder().push(GLType.VEC2F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) + StreamVertexBuilder(GLAttributeList.Builder().push(GLType.VEC2F, GLType.VEC2F).build(), GeometryType.QUADS, 16384) } init { @@ -215,7 +102,7 @@ class GLTextured2dProgram(state: GLStateTracker) : GLShaderProgram(state, listOf } } -class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, listOf(state.internalVertex("shaders/vertex/texture.glsl"), state.internalFragment("shaders/fragment/texture_color.glsl")), GLAttributeList.VERTEX_TEXTURE) { +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") @@ -226,18 +113,13 @@ class GLTexturedColoredProgram(state: GLStateTracker) : GLShaderProgram(state, l } } -class GLPrograms(val state: GLStateTracker) { - val tile by lazy { GLTileProgram(state) } - val font by lazy { GLFontProgram(state) } - val flat by lazy { GLFlatProgram(state) } - val flatColor by lazy { GLFlatColorProgram(state) } - val liquid by lazy { GLLiquidProgram(state) } - val light by lazy { GLLightProgram(state) } - val textured by lazy { GLTexturedProgram(state) } - val textured2d by lazy { GLTextured2dProgram(state) } - val texturedColored by lazy { GLTexturedColoredProgram(state) } - - val viewColorQuad by lazy { GLColorQuadProgram(state) } - val viewTextureQuad by lazy { GLTextureQuadProgram(state) } - val viewTextureBlurQuad by lazy { GLTextureBlurredQuadProgram(state) } +class GLPrograms { + val tile = GLTileProgram() + val font = GLFontProgram() + val flat = GLFlatProgram() + val flatColor = GLFlatColorProgram() + val liquid = GLLiquidProgram() + val textured = GLTexturedProgram() + val textured2d = GLTextured2dProgram() + val texturedColored = GLTexturedColoredProgram() } 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 dc6d4dd1..7c3d5180 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 @@ -1,22 +1,24 @@ package ru.dbotthepony.kstarbound.client.gl.vertex import org.lwjgl.opengl.GL46 -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject +import ru.dbotthepony.kstarbound.client.gl.VertexBufferObject import ru.dbotthepony.kstarbound.client.gl.checkForGLError /** * Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка */ class StreamVertexBuilder( - val state: GLStateTracker, attributes: GLAttributeList, type: GeometryType, initialCapacity: Int = 64, ) { + val state = StarboundClient.current() val builder = VertexBuilder(attributes, type, initialCapacity) - private val vao = state.newVAO() - private val vbo = state.newVBO() - private val ebo = state.newEBO() + private val vao = VertexArrayObject() + private val vbo = VertexBufferObject.vbo() + private val ebo = VertexBufferObject.ebo() init { vao.bind() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt index e70c5053..149b2301 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Box2DRenderer.kt @@ -4,13 +4,14 @@ import org.lwjgl.opengl.GL46.* 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.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2d import kotlin.math.cos import kotlin.math.sin -class Box2DRenderer(val state: GLStateTracker) : IDebugDraw { +class Box2DRenderer : IDebugDraw { + val state = StarboundClient.current() override var drawShapes: Boolean = false override var drawJoints: Boolean = false override var drawAABB: Boolean = false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt index 8f4d3030..a3a0950c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt @@ -28,18 +28,6 @@ class Camera(val client: StarboundClient) { GLFW_KEY_RIGHT -> pressedRight = action > 0 GLFW_KEY_UP -> pressedUp = action > 0 GLFW_KEY_DOWN -> pressedDown = action > 0 - - GLFW_KEY_C -> { - if (action > 0) { - client.settings.debugCollisions = !client.settings.debugCollisions - - if (client.settings.debugCollisions) { - client.putDebugLog("Enable collsion draw") - } else { - client.putDebugLog("Disable collsion draw") - } - } - } } } 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 0dc50967..23b2c716 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Font.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client.render import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import org.lwjgl.opengl.GL46.* +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 @@ -56,10 +57,10 @@ enum class TextAlignY { } class Font( - val state: GLStateTracker, val font: String = "hobo.ttf", val size: Int = 48 ) { + val state = StarboundClient.current() val face = state.freeType.Face(font, 0L) init { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt index f72dd12e..a33e20a5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Mesh.kt @@ -1,17 +1,19 @@ package ru.dbotthepony.kstarbound.client.render import org.lwjgl.opengl.GL46 -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kvector.arrays.Matrix4f -class Mesh(state: GLStateTracker) { - constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) { +class Mesh() { + constructor(builder: VertexBuilder) : this() { load(builder, GL46.GL_STATIC_DRAW) } + val state = StarboundClient.current() + val vao = state.newVAO() val vbo = state.newVBO() val ebo = state.newEBO() @@ -45,8 +47,8 @@ class Mesh(state: GLStateTracker) { } } -data class ConfiguredMesh(val config: RenderConfig, val mesh: Mesh = Mesh(config.state)) { - fun render(transform: Matrix4f = config.state.matrixStack.last()) { +data class ConfiguredMesh(val config: RenderConfig, val mesh: Mesh = Mesh()) { + fun render(transform: Matrix4f = config.client.matrixStack.last()) { config.setup(transform) mesh.render() config.uninstall() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt index fd82f20c..c6d31323 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderConfig.kt @@ -4,10 +4,10 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram import ru.dbotthepony.kvector.arrays.Matrix4f abstract class RenderConfig(val program: T) { - val state get() = program.state + val client get() = program.client open val initialBuilderCapacity: Int get() = 64 - open fun setup(transform: Matrix4f = state.matrixStack.last()) { + open fun setup(transform: Matrix4f = client.matrixStack.last()) { program.use() } 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 804f78ad..9dc3414a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -20,11 +20,9 @@ import kotlin.collections.HashMap /** * Хранит в себе программы для отрисовки определённых [TileDefinition] * - * Создаётся единожды как потомок [GLStateTracker] + * Создаётся единожды как потомок [Graphics] */ class TileRenderers(val client: StarboundClient) { - val state get() = client.gl - private val foreground = HashMap() private val background = HashMap() private val matCache = HashMap() @@ -44,14 +42,14 @@ class TileRenderers(val client: StarboundClient) { } } - private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig(state.programs.tile) { + private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig(client.programs.tile) { override val initialBuilderCapacity: Int get() = 1024 override fun setup(transform: Matrix4f) { super.setup(transform) - state.activeTexture = 0 - state.depthTest = false + client.activeTexture = 0 + client.depthTest = false program.texture = 0 texture.bind() texture.textureMagFilter = GL_NEAREST @@ -97,7 +95,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { HALT } - val state get() = renderers.state + val state get() = renderers.client val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }} val equalityTester: EqualityRuleTester = when (def) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt index ec831857..abcdb6c4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt @@ -3,7 +3,6 @@ package ru.dbotthepony.kstarbound.client.render.entity import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import ru.dbotthepony.kstarbound.client.world.ClientChunk import ru.dbotthepony.kstarbound.client.StarboundClient -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.vector.Vector2d @@ -14,19 +13,12 @@ import ru.dbotthepony.kvector.vector.Vector2d * Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход) */ open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) { - inline val state: GLStateTracker get() = client.gl open val renderPos: Vector2d get() = entity.position open fun render(stack: Matrix4fStack) { } - open fun renderDebug() { - if (chunk?.world?.client?.settings?.debugCollisions == true) { - state.quadWireframe(entity.movement.worldAABB) - } - } - open val layer: Int get() = Z_LEVEL_ENTITIES companion object { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt index 2f8768e6..6a3dcace 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt @@ -9,9 +9,9 @@ class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChu private val def = entity.def override fun render(stack: Matrix4fStack) { - state.programs.textured.use() - state.programs.textured.transform = stack.last() - state.activeTexture = 0 - state.programs.textured.texture = 0 + client.programs.textured.use() + client.programs.textured.transform = stack.last() + client.activeTexture = 0 + client.programs.textured.texture = 0 } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientChunk.kt index dacc6674..e2cfa54b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientChunk.kt @@ -1,14 +1,11 @@ package ru.dbotthepony.kstarbound.client.world -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.entities.Entity class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk(world, pos){ - val state: GLStateTracker get() = world.client.gl - override fun foregroundChanges(cell: Cell) { super.foregroundChanges(cell) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt index 94074d74..ab533dcb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt @@ -36,7 +36,7 @@ class ClientWorld( loopY: Boolean = false ) : World(seed, size, loopX, loopY) { init { - physics.debugDraw = client.gl.box2dRenderer + physics.debugDraw = client.box2dRenderer } private fun determineChunkSize(cells: Int): Int { @@ -72,7 +72,6 @@ class ClientWorld( inner class RenderRegion(val x: Int, val y: Int) { inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) { - private val state get() = client.gl val bakedMeshes = ArrayList, Long>>() var isDirty = true @@ -104,7 +103,7 @@ class ClientWorld( } for ((baked, builder, zLevel) in meshes.meshes()) { - bakedMeshes.add(ConfiguredMesh(baked, Mesh(state, builder)) to zLevel) + bakedMeshes.add(ConfiguredMesh(baked, Mesh(builder)) to zLevel) } } } @@ -134,7 +133,7 @@ class ClientWorld( } for (type in liquidTypes) { - val builder = client.gl.programs.liquid.builder.builder + val builder = client.programs.liquid.builder.builder builder.begin() @@ -148,7 +147,7 @@ class ClientWorld( } } - liquidMesh.add(Mesh(client.gl, builder) to type.color) + liquidMesh.add(Mesh(builder) to type.color) } } @@ -172,7 +171,7 @@ class ClientWorld( layers.add(RenderLayer.Liquid.index) { it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) - val program = client.gl.programs.liquid + val program = client.programs.liquid program.use() program.transform = it.last() @@ -268,7 +267,7 @@ class ClientWorld( 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 -> - client.gl.quadWireframe { + client.quadWireframe { it.quad( obj.pos.x.toFloat(), obj.pos.y.toFloat(), @@ -281,7 +280,7 @@ class ClientWorld( val (x, y) = obj.imagePosition it.render( - client.gl, + client, x = obj.pos.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, y = obj.pos.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf ) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt index 41bc669c..7006adf3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt @@ -9,14 +9,13 @@ import com.google.gson.stream.JsonReader 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.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.math.LineF import ru.dbotthepony.kstarbound.util.contains import ru.dbotthepony.kvector.arrays.Matrix3f -import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2f @@ -30,7 +29,7 @@ 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(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) { + override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { TODO("Not yet implemented") } } @@ -41,7 +40,7 @@ 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(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) { + override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { TODO("Not yet implemented") } } @@ -64,16 +63,16 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig return Image(newPath, transform, centered, position, color, fullbright) } - override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) { + override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) { val sprite = path.sprite ?: return - val texture = gl.loadTexture(path.imagePath.value!!) + val texture = client.loadTexture(path.imagePath.value!!) if (centered) { - gl.quadTexture(texture) { + 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)) } } else { - gl.quadTexture(texture) { + client.quadTexture(texture) { it.quad(x, y, x + sprite.width / PIXELS_IN_STARBOUND_UNITf, y + sprite.height / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite)) } } @@ -81,14 +80,14 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig } class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) { - override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) {} + override fun render(client: StarboundClient, stack: Matrix4fStack, x: Float, y: Float) {} } open fun with(values: (String) -> String?): Drawable { return this } - abstract fun render(gl: GLStateTracker = GLStateTracker.current(), stack: Matrix4fStack = gl.matrixStack, x: Float = 0f, y: Float = 0f) + abstract fun render(client: StarboundClient = StarboundClient.current(), stack: Matrix4fStack = client.matrixStack, x: Float = 0f, y: Float = 0f) companion object { val EMPTY = Empty() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt index 1f3c71b1..fe57c92f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt @@ -18,7 +18,6 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement -import ru.dbotthepony.kstarbound.util.INotNullDelegate import kotlin.properties.Delegates import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 @@ -139,16 +138,10 @@ class BuilderAdapter private constructor( continue } - val delegate = property.property.getDelegate(instance) - - if (delegate is INotNullDelegate && !delegate.isPresent) { - throw JsonSyntaxException("${property.name} in ${instance::class.qualifiedName} can not be null, but it is missing from JSON structure") - } else { - try { - property.property.get(instance) - } catch (err: Throwable) { - throw JsonSyntaxException("${property.name} in ${instance::class.qualifiedName} does not like it being missing from JSON structure", err) - } + try { + property.property.get(instance) + } catch (err: Throwable) { + throw JsonSyntaxException("${property.name} in ${instance::class.qualifiedName} does not like it being missing from JSON structure", err) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt deleted file mode 100644 index 27f4dc04..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -class DoubleEdgeProgression : Iterator { - var value = 0 - - override fun hasNext(): Boolean { - return true - } - - override fun next(): Int { - return nextInt() - } - - fun nextInt(): Int { - return if (value > 0) { - val ret = value - value = -value - ret - } else { - val ret = value - value = -value + 1 - ret - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/INotNullDelegate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/INotNullDelegate.kt deleted file mode 100644 index 5683c8c0..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/INotNullDelegate.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -interface INotNullDelegate { - val isPresent: Boolean -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/IndexedArraySpliterator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/IndexedArraySpliterator.kt deleted file mode 100644 index 7bd140dd..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/IndexedArraySpliterator.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -import java.util.Spliterator -import java.util.function.Consumer - -class IndexedArraySpliterator(private val source: Array, private val offset: Int = 0, private val size: Int = source.size) : Spliterator.Entry> { - inner class Entry(val index: Int, val value: T) - - init { - require(offset + size <= source.size) { "Invalid dimensions for spliterator: offset = $offset, size = $size, source has size of ${source.size}" } - } - - private var index = 0 - - override fun tryAdvance(action: Consumer.Entry>): Boolean { - if (index < size) { - val pointer = offset + index++ - action.accept(Entry(pointer, source[pointer])) - return true - } - - return false - } - - override fun trySplit(): Spliterator.Entry>? { - if (index + 1 >= size) { - return null - } - - val splitSize = size / 2 - - if (splitSize == 0) { - return null - } - - val pointer = index + offset - index += splitSize - return IndexedArraySpliterator(source, pointer, splitSize) - } - - override fun estimateSize(): Long { - return (size - index).toLong() - } - - override fun characteristics(): Int { - return Spliterator.ORDERED or Spliterator.SIZED - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullTwoDimensionalArray.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullTwoDimensionalArray.kt deleted file mode 100644 index 0433b35f..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullTwoDimensionalArray.kt +++ /dev/null @@ -1,69 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -import it.unimi.dsi.fastutil.objects.ObjectSpliterators -import java.util.* -import java.util.stream.Stream -import java.util.stream.StreamSupport -import kotlin.reflect.KClass - -class NotNullTwoDimensionalArray(clazz: KClass, private val width: Int, private val height: Int, initializer: (Int, Int) -> T) { - data class Entry( - val x: Int, - val y: Int, - val value: T, - ) - - private val memory: Array = java.lang.reflect.Array.newInstance(clazz.java, width * height) as Array - - init { - for (x in 0 until width) { - for (y in 0 until height) { - memory[x + y * width] = initializer.invoke(x, y) - } - } - } - - fun isOutside(x: Int, y: Int): Boolean { - return (x !in 0 until width) || (y !in 0 until height) - } - - operator fun get(x: Int, y: Int): T { - if (x !in 0 until width) { - throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width") - } - - if (y !in 0 until height) { - throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height") - } - - return memory[x + y * width] - } - - operator fun set(x: Int, y: Int, value: T): T { - if (x !in 0 until width) { - throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width") - } - - if (y !in 0 until height) { - throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height") - } - - val old = memory[x + y * width] - memory[x + y * width] = value - return old - } - - fun stream(): Stream { - return Arrays.stream(memory) - } - - fun indexedStream(): Stream> { - return StreamSupport.stream(IndexedArraySpliterator(memory), false).map { - val x = it.index % width - val y = (it.index - x) / width - Entry(x, y, it.value) - } - } -} - -inline fun NotNullTwoDimensionalArray(width: Int, height: Int, noinline initializer: (Int, Int) -> T) = NotNullTwoDimensionalArray(T::class, width, height, initializer) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullVar.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullVar.kt deleted file mode 100644 index 9eb7eb7b..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/NotNullVar.kt +++ /dev/null @@ -1,26 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty -import kotlin.properties.Delegates - -/** - * Аналог [Delegates.notNull], но со свойством [isInitialized] - */ -class NotNullVar : ReadWriteProperty, INotNullDelegate { - private var value: V? = null - - /** - * Имеет ли данный делегат не-null значение - */ - override val isPresent: Boolean - get() = value != null - - override fun getValue(thisRef: Any?, property: KProperty<*>): V { - return value ?: throw IllegalStateException("Property ${property.name} was not initialized") - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) { - this.value = value - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt deleted file mode 100644 index 403475dc..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt +++ /dev/null @@ -1,61 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -import it.unimi.dsi.fastutil.objects.ObjectSpliterators -import java.util.Arrays -import java.util.stream.Stream -import java.util.stream.StreamSupport -import kotlin.reflect.KClass - -class TwoDimensionalArray(clazz: KClass, private val width: Int, private val height: Int) { - data class Entry( - val x: Int, - val y: Int, - val value: T, - ) - - private val memory: Array = java.lang.reflect.Array.newInstance(clazz.java, width * height) as Array - - fun isOutside(x: Int, y: Int): Boolean { - return (x !in 0 until width) || (y !in 0 until height) - } - - operator fun get(x: Int, y: Int): T? { - if (x !in 0 until width) { - throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width") - } - - if (y !in 0 until height) { - throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height") - } - - return memory[x + y * width] - } - - operator fun set(x: Int, y: Int, value: T?): T? { - if (x !in 0 until width) { - throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width") - } - - if (y !in 0 until height) { - throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height") - } - - val old = memory[x + y * width] - memory[x + y * width] = value - return old - } - - fun stream(): Stream { - return Arrays.stream(memory) - } - - fun indexedStream(): Stream> { - return StreamSupport.stream(IndexedArraySpliterator(memory), false).map { - val x = it.index % width - val y = (it.index - x) / width - Entry(x, y, it.value) - } - } -} - -inline fun TwoDimensionalArray(width: Int, height: Int) = TwoDimensionalArray(T::class, width, height) diff --git a/src/main/resources/shaders/screen_quad.fsh b/src/main/resources/shaders/screen_quad.fsh deleted file mode 100644 index fdc26879..00000000 --- a/src/main/resources/shaders/screen_quad.fsh +++ /dev/null @@ -1,10 +0,0 @@ - -#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 deleted file mode 100644 index 23540e7e..00000000 --- a/src/main/resources/shaders/screen_quad.vsh +++ /dev/null @@ -1,8 +0,0 @@ - -#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 deleted file mode 100644 index dba4b580..00000000 --- a/src/main/resources/shaders/screen_quad_tex.fsh +++ /dev/null @@ -1,11 +0,0 @@ - -#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 deleted file mode 100644 index 8778a315..00000000 --- a/src/main/resources/shaders/screen_quad_tex.vsh +++ /dev/null @@ -1,12 +0,0 @@ - -#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; -}