From 67ac2b272e88e70d4d5fc9cf3b6c5ff76aec7d50 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 12 May 2024 21:10:37 +0700 Subject: [PATCH] Busy.png makes a nice loading icon --- .../kstarbound/client/StarboundClient.kt | 109 +++++++++++++++--- src/main/resources/starbound_busy.png | Bin 0 -> 1796 bytes src/main/resources/starbound_busy.xcf | Bin 0 -> 39533 bytes 3 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 src/main/resources/starbound_busy.png create mode 100644 src/main/resources/starbound_busy.xcf diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 0aaef68a..a620d6f2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -62,6 +62,7 @@ import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.util.BlockableEventLoop import ru.dbotthepony.kstarbound.util.formatBytesShort +import ru.dbotthepony.kstarbound.util.supplyAsync import ru.dbotthepony.kstarbound.world.ChunkState import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.RayDirection @@ -78,12 +79,14 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import java.time.Duration import java.util.* +import java.util.concurrent.CompletableFuture import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinWorkerThread import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantLock import java.util.function.Consumer +import java.util.function.Function import java.util.function.IntConsumer import kotlin.collections.ArrayList import kotlin.concurrent.withLock @@ -335,6 +338,33 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo texture } + val busyTexture: CompletableFuture by lazy(LazyThreadSafetyMode.NONE) { + Starbound.IO_EXECUTOR.supplyAsync { + MemoryStack.stackPush().use { stack -> + val pWidth = stack.mallocInt(1) + val pHeight = stack.mallocInt(1) + val pChannels = stack.mallocInt(1) + + val readFromDisk = readInternalBytes("starbound_busy.png") + val buff = ByteBuffer.allocateDirect(readFromDisk.size) + buff.put(readFromDisk) + buff.position(0) + + val data = STBImage.stbi_load_from_memory(buff, pWidth, pHeight, pChannels, 4) + ?: throw IllegalStateException("Unable to decode starbound_busy.png") + + (pWidth[0] to pHeight[0]) to data + } + }.thenApplyAsync(Function { (size, data) -> + val (w, h) = size + val texture = GLTexture2D(w, h, GL_RGBA8) + + texture.upload(GL_RGBA, GL_UNSIGNED_BYTE, data) + texture.textureMagFilter = GL_NEAREST + texture + }, this) + } + val missingTexture by lazy(LazyThreadSafetyMode.NONE) { val texture = GLTexture2D(8, 8, GL_RGB8) val buffer = ByteBuffer.allocateDirect(3 * 8 * 8) @@ -621,26 +651,14 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo private var renderedLoadingScreen = false - private fun renderLoadingScreen() { - performOpenGLCleanup() - updateViewportParams() - - clearColor = RGBAColor.BLACK - glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) - + private fun renderBusyDots() { val min = viewportHeight.coerceAtMost(viewportWidth) val size = min * 0.02f val program = programs.positionColor val builder = program.builder - uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } - fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } - - stack.clear(Matrix3f.identity()) - program.colorMultiplier = RGBAColor.WHITE - builder.builder.begin(GeometryType.QUADS) if (dotCountdown-- <= 0) { @@ -664,6 +682,69 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo builder.builder.centeredQuad(viewportWidth * 0.5f - size * 2f, viewportHeight * 0.5f, size, size) { color(a) } builder.builder.centeredQuad(viewportWidth * 0.5f + size * 2f, viewportHeight * 0.5f, size, size) { color(c) } + program.use() + builder.upload() + builder.draw() + } + + private fun renderBusyLogo() { + val min = viewportHeight.coerceAtMost(viewportWidth) + val size = min * 0.0035f + val width = size * 46f + val height = size * 28f + + val program = programs.positionTexture + val builder = program.builder + + program.texture0 = 0 + textures2D[0] = busyTexture.get() + program.colorMultiplier = RGBAColor.WHITE + + builder.builder.begin(GeometryType.QUADS) + + val x = viewportWidth * 0.5f + val y = viewportHeight * 0.5f + + val index = ((System.nanoTime() / 40_000_000L) % 29L) / 29f + + builder.builder.vertex(x - width, y - height).uv(index, 0f) + builder.builder.vertex(x - width, y + height).uv(index, 1f) + builder.builder.vertex(x + width, y + height).uv(index + 1f / 29f, 1f) + builder.builder.vertex(x + width, y - height).uv(index + 1f / 29f, 0f) + + program.use() + builder.upload() + builder.draw() + } + + fun renderBusyIcon() { + if (busyTexture.isDone && !busyTexture.isCompletedExceptionally) { + renderBusyLogo() + } else { + renderBusyDots() + } + } + + private fun renderLoadingScreen() { + performOpenGLCleanup() + updateViewportParams() + + clearColor = RGBAColor.BLACK + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) + + uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } + fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } + + stack.clear(Matrix3f.identity()) + + renderBusyIcon() + + val program = programs.positionColor + val builder = program.builder + + program.colorMultiplier = RGBAColor.WHITE + + builder.builder.begin(GeometryType.QUADS) builder.builder.quad(0f, viewportHeight - 20f, viewportWidth * Starbound.loadingProgress.toFloat(), viewportHeight.toFloat()) { color(RGBAColor.GREEN) } GLFW.glfwSetWindowTitle(window, "KStarbound: ${Starbound.loadingProgressText}") @@ -671,7 +752,6 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo val runtime = Runtime.getRuntime() - //if (runtime.maxMemory() <= 4L * 1024L * 1024L * 1024L) { builder.builder.centeredQuad(viewportWidth * 0.5f, viewportHeight * 0.1f, viewportWidth * 0.7f, 2f) { color(RGBAColor.WHITE) } builder.builder.centeredQuad(viewportWidth * 0.5f, viewportHeight * 0.1f + 20f, viewportWidth * 0.7f, 2f) { color(RGBAColor.WHITE) } builder.builder.centeredQuad(viewportWidth * (0.5f - 0.35f), viewportHeight * 0.1f + 10f, 2f, 20f) { color(RGBAColor.WHITE) } @@ -687,7 +767,6 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo leftEdge + viewportWidth * 0.7f * ((runtime.totalMemory() - runtime.freeMemory()) / runtime.maxMemory().toDouble()).toFloat(), viewportHeight * 0.1f + 19f ) { color(RGBAColor(29, 140, 160)) } - //} if (fontInitialized) { drawPerformanceBasic(true) diff --git a/src/main/resources/starbound_busy.png b/src/main/resources/starbound_busy.png new file mode 100644 index 0000000000000000000000000000000000000000..3c98661f984cd5f12789845883eb6b05c710585c GIT binary patch literal 1796 zcma)62~d+q6#nCIc!2HbSR_sH0x^|S6Hzck5e!HZwITu`1RQR;OvF$k1Vz+p0jUR) z1PD?&iiAou@dsm2D~gyz3=#?9D2IYT0z&-d2x&swsh#O`x;wl3_U+Do`@Zk(e!q`^ zGhYl}3;=++kGJQy0ARdDZzC3(=zF3~j}!o4r(<^Q+UK)t*XkrnVq{EQ1ORNvOUuJ} zKiYwKOg`Z26@(b!^j<7+ZTIp*_^!ART5@|uP_h#^xrB+bMmpr2I`ZH_?&F^`FYvfa zxu;ftg!PW}{4}`MX3i0L``mcX-BoH8M%4xLsA}}%IWDbWMuh*k$eA3lBC}@U>eEiU zo$Qg<*to`_k#|q3-S%)#OL)d!>zvJjjY-)Sh$*A2w1VXPoHJ9j+ZytT&h~UJIA=S#Dg;1!x*b4 z@=%z8^;yey^gQNc8&CE)yhu=dfNsSL_5!-iNjH+UnlGFnpHyCzS8m3G_@3UnswR5F zz*48#pM1~sd#5u!DvjFVt6=nzG@ zYPCS74W&Bo9PFK57L&uiI8Q)ls2pMWCB0`+l6N2#0G2E_G#J2SuhctDkNM!eOb3n4 z%q@`-#U&iQv@jpfoqj2b(asXCwbx3M7emI6mR#gGZ7|wuv@BDBUs#m?`t9rdhULrl zt+ZZXx*%(3-q?Z-D~*s3+Y0v_Og?n*keJ=)npQttkKxhOsuPtu3QQ05N8!|n*8wTt zFtn4i)4IN>RVOCYWU~%F$sxa4lPV0+58YT%%4nU7`|NQRlE&Uh3rPS6t`g@v)g7wt zy~fssD%7<@#HK0U3C+7w7U<5H>hq_}**S_*)REjn+$TWHvVtY?UznNUAGFA^L8`H)8QES18T-w^9x{WVdb$3j zlG1EXbLbu~F4Y?XB?Y=1d$=tGTL%{W*QkpYM#)27x2%qCr?tRXnHs z!kSmt7Fa;gwKrBD7G~$3oQq}U^U&x=(riDj@;d+^Ok*VL4Eq58(p!sY z;jfULNuIOmg<36(xfY%yWp4Ufpj=0lu17h`Kyq-@99XNEn5QejzKCL#lS|mI{vU?O z;_uu1mUHc;_S!?+!LZNn2Es3lxAxtIU7YS`OhS1qNifrmMjDpaA5R12Pf2d}+claZ zJu3!kHugKj`!!}i_y52pD=L2YvY>i|B)Yxd(q$8|y*@prbW=h&zm}$bxjRDnG;Ux- zgKK$hWC-l4KCsUV;%i!hivpnxUo^V-O%Bxm<;?58x6F=uC&81j6ydksxP(~x&{P|U z8!fyNBtSv!vdTVjHj(XX7)xqaPMxlDFO@D-eYdL6p&H=&Bo(ddHoCf(A{( z6V$IxsHm)_8LG5}llNyLT2iHLO1n-OmH#GksHljCcJYNK+0|fRUHRKMv6vncYw`)9 zXG-aI5+d616Bs)ZpnLvMxh zVYK!hlqxJMhC&jL9eCfxEI|JARB<7?I-f5|=u(leU2aGH=7eu+dP5k!)n3)q9xH6MVhNe->Nekhbp&op74o9v78s~%*2{Iq zvNL0QU00y2!~U8lIH_>F=_mf7SUkI+5nS$=l%c@gjI~Jwi|Y E4zl%(J^%m! literal 0 HcmV?d00001 diff --git a/src/main/resources/starbound_busy.xcf b/src/main/resources/starbound_busy.xcf new file mode 100644 index 0000000000000000000000000000000000000000..d5ef6c48e3ee3a7ec78ef14443a20f8f4142551c GIT binary patch literal 39533 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