Better frame scheduling
This commit is contained in:
parent
a41037826c
commit
1de2b4c167
@ -242,8 +242,8 @@ fun main() {
|
|||||||
//client.camera.pos.y = ent.position.y.toFloat()
|
//client.camera.pos.y = ent.position.y.toFloat()
|
||||||
|
|
||||||
client.camera.pos += Vector2f(
|
client.camera.pos += Vector2f(
|
||||||
(if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f) + (if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f),
|
(if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f) + (if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f),
|
||||||
(if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f) + (if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.zoom else 0f)
|
(if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f) + (if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f)
|
||||||
)
|
)
|
||||||
|
|
||||||
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
||||||
|
@ -85,6 +85,8 @@ import ru.dbotthepony.kstarbound.util.set
|
|||||||
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
import java.util.function.BinaryOperator
|
import java.util.function.BinaryOperator
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
@ -95,7 +97,8 @@ import kotlin.collections.ArrayList
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
object Starbound : ISBFileLocator {
|
object Starbound : ISBFileLocator {
|
||||||
const val TICK_TIME_ADVANCE = 1.0 / 60.0
|
const val TICK_TIME_ADVANCE = 0.01666666666666664
|
||||||
|
const val TICK_TIME_ADVANCE_NANOS = 16_666_666L
|
||||||
|
|
||||||
// currently it saves only 4 megabytes of ram on pretty big modpack
|
// currently it saves only 4 megabytes of ram on pretty big modpack
|
||||||
// Hrm.
|
// Hrm.
|
||||||
|
@ -49,7 +49,6 @@ import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
|||||||
import ru.dbotthepony.kstarbound.defs.image.Image
|
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
|
||||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
||||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
@ -742,26 +741,41 @@ class StarboundClient : Closeable {
|
|||||||
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
|
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
|
||||||
}
|
}
|
||||||
|
|
||||||
var frameRenderTime = 0.0
|
// nanoseconds
|
||||||
|
var frameRenderTime = 0L
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private var lastRender = JVMTimeSource.INSTANCE.seconds
|
private var nextRender = System.nanoTime()
|
||||||
val framesPerSecond get() = if (frameRenderTime == 0.0) 1.0 else 1.0 / frameRenderTime
|
private val frameRenderTimes = LongArray(60) { 1L }
|
||||||
|
|
||||||
private val frameRenderTimes = DoubleArray(60) { 1.0 }
|
|
||||||
private var frameRenderIndex = 0
|
private var frameRenderIndex = 0
|
||||||
|
private val renderWaitTimes = LongArray(60) { 1L }
|
||||||
|
private var renderWaitIndex = 0
|
||||||
|
private var lastRender = System.nanoTime()
|
||||||
|
|
||||||
val averageFramesPerSecond: Double get() {
|
val averageRenderWait: Double get() {
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
|
|
||||||
for (value in frameRenderTimes) {
|
for (value in renderWaitTimes)
|
||||||
sum += value
|
sum += value
|
||||||
}
|
|
||||||
|
|
||||||
if (sum == 0.0)
|
if (sum == 0.0)
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
return frameRenderTimes.size / sum
|
sum /= 1_000_000_000.0
|
||||||
|
return sum / renderWaitTimes.size
|
||||||
|
}
|
||||||
|
|
||||||
|
val averageRenderTime: Double get() {
|
||||||
|
var sum = 0.0
|
||||||
|
|
||||||
|
for (value in frameRenderTimes)
|
||||||
|
sum += value
|
||||||
|
|
||||||
|
if (sum == 0.0)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
sum /= 1_000_000_000.0
|
||||||
|
return sum / frameRenderTimes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
val settings = ClientSettings()
|
val settings = ClientSettings()
|
||||||
@ -828,164 +842,181 @@ class StarboundClient : Closeable {
|
|||||||
fun renderFrame(): Boolean {
|
fun renderFrame(): Boolean {
|
||||||
ensureSameThread()
|
ensureSameThread()
|
||||||
|
|
||||||
val diff = JVMTimeSource.INSTANCE.seconds - lastRender
|
var diff = nextRender - System.nanoTime()
|
||||||
|
var yields = 0
|
||||||
|
|
||||||
if (diff < Starbound.TICK_TIME_ADVANCE)
|
// try to sleep until next frame as precise as possible
|
||||||
LockSupport.parkNanos(((Starbound.TICK_TIME_ADVANCE - diff) * 1_000_000_000.0).toLong())
|
while (diff > 0L) {
|
||||||
|
if (diff >= 1_500_000L) {
|
||||||
|
LockSupport.parkNanos(1_000_000L)
|
||||||
|
} else {
|
||||||
|
Thread.yield()
|
||||||
|
yields++
|
||||||
|
}
|
||||||
|
|
||||||
frameRenderTime = JVMTimeSource.INSTANCE.seconds - lastRender
|
diff = nextRender - System.nanoTime()
|
||||||
frameRenderTimes[++frameRenderIndex % frameRenderTimes.size] = frameRenderTime
|
|
||||||
lastRender = JVMTimeSource.INSTANCE.seconds
|
|
||||||
|
|
||||||
if (GLFW.glfwWindowShouldClose(window)) {
|
|
||||||
close()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val world = world
|
val mark = System.nanoTime()
|
||||||
|
|
||||||
if (!isRenderingGame) {
|
try {
|
||||||
cleanup()
|
if (GLFW.glfwWindowShouldClose(window)) {
|
||||||
GLFW.glfwPollEvents()
|
close()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val world = world
|
||||||
|
|
||||||
|
if (!isRenderingGame) {
|
||||||
|
cleanup()
|
||||||
|
GLFW.glfwPollEvents()
|
||||||
|
|
||||||
|
if (world != null) {
|
||||||
|
if (Starbound.initialized)
|
||||||
|
world.think()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if (world != null) {
|
if (world != null) {
|
||||||
|
updateViewportParams()
|
||||||
|
val layers = LayeredRenderer()
|
||||||
|
|
||||||
if (Starbound.initialized)
|
if (Starbound.initialized)
|
||||||
world.think()
|
world.think()
|
||||||
|
|
||||||
|
clearColor = RGBAColor.SLATE_GRAY
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||||
|
matrixStack.clear(viewportMatrixWorld)
|
||||||
|
|
||||||
|
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) // перемещаем вид к камере
|
||||||
|
|
||||||
|
for (lambda in onPreDrawWorld) {
|
||||||
|
lambda.invoke(layers)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in onPostDrawWorldOnce.size - 1 downTo 0) {
|
||||||
|
onPostDrawWorldOnce[i].invoke(layers)
|
||||||
|
onPostDrawWorldOnce.removeAt(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportLighting.clear()
|
||||||
|
|
||||||
|
world.addLayers(
|
||||||
|
layers = layers,
|
||||||
|
size = viewportRectangle)
|
||||||
|
|
||||||
|
layers.render(matrixStack)
|
||||||
|
|
||||||
|
val viewportLightingMem = viewportLightingMem
|
||||||
|
|
||||||
|
if (viewportLightingMem != null && !fullbright) {
|
||||||
|
viewportLightingMem.position(0)
|
||||||
|
BufferUtils.zeroBuffer(viewportLightingMem)
|
||||||
|
viewportLightingMem.position(0)
|
||||||
|
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
|
||||||
|
viewportLightingMem.position(0)
|
||||||
|
|
||||||
|
val old = textureUnpackAlignment
|
||||||
|
textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
||||||
|
|
||||||
|
viewportLightingTexture.upload(
|
||||||
|
GL_RGB,
|
||||||
|
viewportLighting.width.coerceAtMost(4096),
|
||||||
|
viewportLighting.height.coerceAtMost(4096),
|
||||||
|
GL_RGB,
|
||||||
|
GL_UNSIGNED_BYTE,
|
||||||
|
viewportLightingMem
|
||||||
|
)
|
||||||
|
|
||||||
|
textureUnpackAlignment = old
|
||||||
|
|
||||||
|
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
||||||
|
//viewportLightingTexture.textureMagFilter = GL_NEAREST
|
||||||
|
|
||||||
|
//viewportLightingTexture.generateMips()
|
||||||
|
|
||||||
|
blendFunc = BlendFunc.MULTIPLY_BY_SRC
|
||||||
|
|
||||||
|
quadTexture(viewportLightingTexture) {
|
||||||
|
it.quad(
|
||||||
|
(viewportCellX).toFloat(),
|
||||||
|
(viewportCellY).toFloat(),
|
||||||
|
(viewportCellX + viewportCellWidth).toFloat(),
|
||||||
|
(viewportCellY + viewportCellHeight).toFloat(),
|
||||||
|
QuadTransformers.uv()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
|
||||||
|
}
|
||||||
|
|
||||||
|
world.physics.debugDraw()
|
||||||
|
|
||||||
|
for (lambda in onPostDrawWorld) {
|
||||||
|
lambda.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
matrixStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matrixStack.clear(viewportMatrixScreen)
|
||||||
|
|
||||||
|
val thisTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
if (startupTextList.isNotEmpty() && thisTime <= finishStartupRendering) {
|
||||||
|
var alpha = 1f
|
||||||
|
|
||||||
|
if (finishStartupRendering - thisTime < 1000L) {
|
||||||
|
alpha = (finishStartupRendering - thisTime) / 1000f
|
||||||
|
}
|
||||||
|
|
||||||
|
matrixStack.push()
|
||||||
|
matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat())
|
||||||
|
var shade = 255
|
||||||
|
|
||||||
|
for (i in startupTextList.size - 1 downTo 0) {
|
||||||
|
val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha))
|
||||||
|
matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f)
|
||||||
|
|
||||||
|
if (shade > 120) {
|
||||||
|
shade -= 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matrixStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fn in onDrawGUI) {
|
||||||
|
fn.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
val runtime = Runtime.getRuntime()
|
||||||
|
|
||||||
|
font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
|
||||||
|
font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f)
|
||||||
|
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f)
|
||||||
|
|
||||||
|
GLFW.glfwSwapBuffers(window)
|
||||||
|
GLFW.glfwPollEvents()
|
||||||
|
input.think()
|
||||||
|
|
||||||
|
camera.think(Starbound.TICK_TIME_ADVANCE)
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
} finally {
|
||||||
|
frameRenderTime = System.nanoTime() - mark
|
||||||
|
frameRenderTimes[++frameRenderIndex % frameRenderTimes.size] = frameRenderTime
|
||||||
|
renderWaitTimes[++renderWaitIndex % renderWaitTimes.size] = System.nanoTime() - lastRender
|
||||||
|
lastRender = System.nanoTime()
|
||||||
|
nextRender = mark + Starbound.TICK_TIME_ADVANCE_NANOS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (world != null) {
|
|
||||||
updateViewportParams()
|
|
||||||
val layers = LayeredRenderer()
|
|
||||||
|
|
||||||
if (Starbound.initialized)
|
|
||||||
world.think()
|
|
||||||
|
|
||||||
clearColor = RGBAColor.SLATE_GRAY
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
|
||||||
matrixStack.clear(viewportMatrixWorld)
|
|
||||||
|
|
||||||
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) // перемещаем вид к камере
|
|
||||||
|
|
||||||
for (lambda in onPreDrawWorld) {
|
|
||||||
lambda.invoke(layers)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in onPostDrawWorldOnce.size - 1 downTo 0) {
|
|
||||||
onPostDrawWorldOnce[i].invoke(layers)
|
|
||||||
onPostDrawWorldOnce.removeAt(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewportLighting.clear()
|
|
||||||
|
|
||||||
world.addLayers(
|
|
||||||
layers = layers,
|
|
||||||
size = viewportRectangle)
|
|
||||||
|
|
||||||
layers.render(matrixStack)
|
|
||||||
|
|
||||||
val viewportLightingMem = viewportLightingMem
|
|
||||||
|
|
||||||
if (viewportLightingMem != null && !fullbright) {
|
|
||||||
viewportLightingMem.position(0)
|
|
||||||
BufferUtils.zeroBuffer(viewportLightingMem)
|
|
||||||
viewportLightingMem.position(0)
|
|
||||||
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
|
|
||||||
viewportLightingMem.position(0)
|
|
||||||
|
|
||||||
val old = textureUnpackAlignment
|
|
||||||
textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
|
||||||
|
|
||||||
viewportLightingTexture.upload(
|
|
||||||
GL_RGB,
|
|
||||||
viewportLighting.width.coerceAtMost(4096),
|
|
||||||
viewportLighting.height.coerceAtMost(4096),
|
|
||||||
GL_RGB,
|
|
||||||
GL_UNSIGNED_BYTE,
|
|
||||||
viewportLightingMem
|
|
||||||
)
|
|
||||||
|
|
||||||
textureUnpackAlignment = old
|
|
||||||
|
|
||||||
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
|
||||||
//viewportLightingTexture.textureMagFilter = GL_NEAREST
|
|
||||||
|
|
||||||
//viewportLightingTexture.generateMips()
|
|
||||||
|
|
||||||
blendFunc = BlendFunc.MULTIPLY_BY_SRC
|
|
||||||
|
|
||||||
quadTexture(viewportLightingTexture) {
|
|
||||||
it.quad(
|
|
||||||
(viewportCellX).toFloat(),
|
|
||||||
(viewportCellY).toFloat(),
|
|
||||||
(viewportCellX + viewportCellWidth).toFloat(),
|
|
||||||
(viewportCellY + viewportCellHeight).toFloat(),
|
|
||||||
QuadTransformers.uv()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
|
|
||||||
}
|
|
||||||
|
|
||||||
world.physics.debugDraw()
|
|
||||||
|
|
||||||
for (lambda in onPostDrawWorld) {
|
|
||||||
lambda.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixStack.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixStack.clear(viewportMatrixScreen)
|
|
||||||
|
|
||||||
val thisTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
if (startupTextList.isNotEmpty() && thisTime <= finishStartupRendering) {
|
|
||||||
var alpha = 1f
|
|
||||||
|
|
||||||
if (finishStartupRendering - thisTime < 1000L) {
|
|
||||||
alpha = (finishStartupRendering - thisTime) / 1000f
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixStack.push()
|
|
||||||
matrixStack.last().translateWithMultiplication(y = viewportHeight.toFloat())
|
|
||||||
var shade = 255
|
|
||||||
|
|
||||||
for (i in startupTextList.size - 1 downTo 0) {
|
|
||||||
val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha))
|
|
||||||
matrixStack.last().translateWithMultiplication(y = -size.height * 1.2f)
|
|
||||||
|
|
||||||
if (shade > 120) {
|
|
||||||
shade -= 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixStack.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (fn in onDrawGUI) {
|
|
||||||
fn.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
val runtime = Runtime.getRuntime()
|
|
||||||
|
|
||||||
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()
|
|
||||||
input.think()
|
|
||||||
|
|
||||||
camera.think(Starbound.TICK_TIME_ADVANCE)
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTermination(lambda: () -> Unit) {
|
fun onTermination(lambda: () -> Unit) {
|
||||||
|
Loading…
Reference in New Issue
Block a user