From 0a005955209fa1b12f3eeec248dfc45395f726e1 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 21 Feb 2023 19:15:35 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B1=D0=B5=D1=80=D0=B0=D0=B5=D0=BC=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B2=20=D1=81=D0=B0=D0=BC=D0=BE=D0=BC=20=D0=BD=D0=B8?= =?UTF-8?q?=D0=B7=D1=83=20render=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 4 +- .../kstarbound/client/ClientWorld.kt | 12 +- .../kstarbound/client/StarboundClient.kt | 181 +++++++----------- .../kstarbound/client/gl/GLStateTracker.kt | 72 +++++++ 4 files changed, 143 insertions(+), 126 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 85646f1c..956cbfeb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -59,9 +59,9 @@ fun main() { var parse = 0L //for (chunkX in 17 .. 18) { - for (chunkX in 0 .. 60) { + for (chunkX in 14 .. 24) { // for (chunkY in 21 .. 21) { - for (chunkY in 0 .. 60) { + for (chunkY in 18 .. 24) { var t = System.currentTimeMillis() val data = db.read(byteArrayOf(1, 0, chunkX.toByte(), 0, chunkY.toByte())) find += System.currentTimeMillis() - t diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt index 6cc3b4d6..ee4b9b57 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt @@ -22,17 +22,10 @@ class ClientWorld( ) } - /** - * Отрисовывает этот с обрезкой невидимой геометрии с точки зрения [size] в Starbound Units - * - * Все координаты "местности" сохраняются, поэтому, если отрисовывать слишком далеко от 0, 0 - * то геометрия может начать искажаться из-за погрешности плавающей запятой - */ - fun render( + fun addLayers( size: AABB, - isScreenspaceRender: Boolean = true + layers: LayeredRenderer ) { - val layers = LayeredRenderer() client.lightRenderer.begin() for (chunk in collectPositionAware(size.encasingChunkPosAABB())) { @@ -42,7 +35,6 @@ class ClientWorld( //client.lightRenderer.addShadowGeometry(renderer) } - layers.render(client.gl.matrixStack) /* for ((lightPosition, color) in listOf( (client.screenToWorld(client.mouseCoordinatesF)) to Color.RED, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index b5d07af1..14ea3e5a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.client.gl.ScissorRect import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.GPULightRenderer +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.util.JVMTimeSource @@ -40,26 +41,21 @@ class StarboundClient(val starbound: Starbound) : Closeable { val window: Long val camera = Camera(this) val input = UserInput() + val gl: GLStateTracker var gameTerminated = false private set - var viewportWidth = 800 - private set - - var viewportHeight = 600 - private set - /** * Матрица преобразования экранных координат (в пикселях) в нормализованные координаты */ - var viewportMatrixScreen = updateViewportMatrixScreen() + var viewportMatrixScreen: Matrix4f private set /** * Матрица преобразования мировых координат в нормализованные координаты */ - var viewportMatrixWorld = updateViewportMatrixWorld() + var viewportMatrixWorld: Matrix4f private set private val startupTextList = ArrayList() @@ -172,29 +168,29 @@ class StarboundClient(val starbound: Starbound) : Closeable { GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 6) GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE) - window = GLFW.glfwCreateWindow(viewportWidth, viewportHeight, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) + 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(starbound) + GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h -> if (w == 0 || h == 0) { isRenderingGame = false - return@glfwSetFramebufferSizeCallback - } + } else { + isRenderingGame = true + viewportMatrixScreen = updateViewportMatrixScreen() + viewportMatrixWorld = updateViewportMatrixWorld() + gl.setViewport(0, 0, w, h) - isRenderingGame = true - viewportWidth = w - viewportHeight = h - viewportMatrixScreen = updateViewportMatrixScreen() - viewportMatrixWorld = updateViewportMatrixWorld() - glViewport(0, 0, w, h) + lightRenderer.resizeFramebuffer(w, h) - lightRenderer.resizeFramebuffer(viewportWidth, viewportHeight) - - for (callback in onViewportChanged) { - callback.invoke(w, h) + for (callback in onViewportChanged) { + callback.invoke(w, h) + } } } @@ -213,12 +209,14 @@ class StarboundClient(val starbound: Starbound) : Closeable { (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() } - GLFW.glfwMakeContextCurrent(window) - // vsync GLFW.glfwSwapInterval(0) @@ -226,7 +224,9 @@ class StarboundClient(val starbound: Starbound) : Closeable { putDebugLog("Initialized GLFW window") } - val gl = GLStateTracker(starbound) + val viewportWidth by gl::viewportWidth + val viewportHeight by gl::viewportHeight + val tileRenderers = TileRenderers(this) val lightRenderer = GPULightRenderer(gl) @@ -236,8 +236,6 @@ class StarboundClient(val starbound: Starbound) : Closeable { var world: ClientWorld? = ClientWorld(this, 0L, 0) - fun ensureSameThread() = gl.ensureSameThread() - init { putDebugLog("Initialized OpenGL context") gl.clearColor = Color.SLATE_GREY @@ -269,87 +267,34 @@ class StarboundClient(val starbound: Starbound) : Closeable { val settings = ClientSettings() + 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) } - private val onDrawGUI = ArrayList<() -> Unit>() - fun onDrawGUI(lambda: () -> Unit) { onDrawGUI.add(lambda) } - private val onPreDrawWorld = ArrayList<() -> Unit>() - - fun onPreDrawWorld(lambda: () -> Unit) { + fun onPreDrawWorld(lambda: (LayeredRenderer) -> Unit) { onPreDrawWorld.add(lambda) } - private val onPostDrawWorld = ArrayList<() -> Unit>() - fun onPostDrawWorld(lambda: () -> Unit) { onPostDrawWorld.add(lambda) } - private val onPostDrawWorldOnce = ArrayList<() -> Unit>() - - fun onPostDrawWorldOnce(lambda: () -> Unit) { + fun onPostDrawWorldOnce(lambda: (LayeredRenderer) -> Unit) { onPostDrawWorldOnce.add(lambda) } - private val scissorStack = LinkedList() - - 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) - gl.scissorRect = rect - gl.scissor = true - } - - fun popScissorRect() { - scissorStack.removeLast() - - val peek = scissorStack.lastOrNull() - - if (peek == null) { - gl.scissor = false - return - } - - val y = viewportHeight - peek.y - peek.height - gl.scissorRect = ScissorRect(peek.x, y, peek.width, peek.height) - } - - val currentScissorRect get() = scissorStack.lastOrNull() - fun renderFrame(): Boolean { - ensureSameThread() + gl.ensureSameThread() if (GLFW.glfwWindowShouldClose(window)) { close() @@ -364,40 +309,48 @@ class StarboundClient(val starbound: Starbound) : Closeable { } val measure = JVMTimeSource.INSTANCE.seconds + val world = world - if (frameRenderTime != 0.0 && starbound.initialized) - world?.think(frameRenderTime) + if (world != null) { + val layers = LayeredRenderer() - gl.clearColor = Color.SLATE_GREY - glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) - gl.matrixStack.clear(viewportMatrixWorld.toMutableMatrix4f()) + if (frameRenderTime != 0.0 && starbound.initialized) + world.think(frameRenderTime) - gl.matrixStack.push() - .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) // перемещаем вид к камере + gl.clearColor = Color.SLATE_GREY + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) + gl.matrixStack.clear(viewportMatrixWorld.toMutableMatrix4f()) - for (lambda in onPreDrawWorld) { - lambda.invoke() + gl.matrixStack.push() + .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) // перемещаем вид к камере + + for (lambda in onPreDrawWorld) { + lambda.invoke(layers) + } + + for (i in onPostDrawWorldOnce.size - 1 downTo 0) { + onPostDrawWorldOnce[i].invoke(layers) + onPostDrawWorldOnce.removeAt(i) + } + + world.addLayers( + layers = layers, + size = AABB.rectangle( + camera.pos.toDoubleVector(), + viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT, + viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT)) + + layers.render(gl.matrixStack) + + for (lambda in onPostDrawWorld) { + lambda.invoke() + } + + gl.matrixStack.pop() } - for (i in onPostDrawWorldOnce.size - 1 downTo 0) { - onPostDrawWorldOnce[i].invoke() - onPostDrawWorldOnce.removeAt(i) - } - - world?.render( - AABB.rectangle( - camera.pos.toDoubleVector(), - viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT, - viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT)) - - for (lambda in onPostDrawWorld) { - lambda.invoke() - } - - gl.matrixStack.pop() - gl.matrixStack.clear(viewportMatrixScreen.toMutableMatrix4f()) val thisTime = System.currentTimeMillis() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt index 571126cc..cd28007e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt @@ -24,10 +24,12 @@ import ru.dbotthepony.kvector.vector.Color import java.io.File import java.io.FileNotFoundException import java.lang.ref.Cleaner +import java.util.* import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicLong import kotlin.collections.ArrayList import kotlin.collections.HashMap +import kotlin.math.roundToInt import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -150,6 +152,7 @@ class GLStateTracker(val locator: ISBFileLocator) { val thread: Thread = Thread.currentThread() val box2dRenderer = Box2DRenderer(this) + private val scissorStack = LinkedList() private val cleanerBacklog = ArrayList<() -> Unit>() @Volatile @@ -347,6 +350,75 @@ class GLStateTracker(val locator: ISBFileLocator) { 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!")