From 0b163481e75fe054f6b8b8c16cf8b45eca938a78 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 12 Oct 2023 08:10:37 +0700 Subject: [PATCH] Have textures be logically loaded from Image itself --- .../ru/dbotthepony/kstarbound/Starbound.kt | 2 + .../kstarbound/client/StarboundClient.kt | 46 ++------------ .../kstarbound/client/render/TileRenderer.kt | 13 ++-- .../dbotthepony/kstarbound/defs/Drawable.kt | 2 +- .../kstarbound/defs/image/Image.kt | 60 ++++++++++++++++++- .../kstarbound/defs/tile/RenderTemplate.kt | 3 +- 6 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index f87c74e1..d94aa9f3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -190,6 +190,8 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(ItemReference.Factory(STRINGS)) registerTypeAdapterFactory(TreasurePoolDefinition.Companion) + registerTypeAdapter(Image.Companion) + registerTypeAdapterFactory(with(RegistryReferenceFactory()) { add(Registries.tiles) add(Registries.tileModifiers) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 121fdeaa..4c1f6b4d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.Scheduler import org.apache.logging.log4j.LogManager import org.lwjgl.BufferUtils import org.lwjgl.glfw.Callbacks @@ -73,6 +74,7 @@ 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.atomic.AtomicInteger @@ -282,13 +284,15 @@ class StarboundClient : Closeable { val stack = Matrix3fStack() // минимальное время хранения 5 минут и... - private val named2DTextures0: Cache = Caffeine.newBuilder() + val named2DTextures0: Cache = Caffeine.newBuilder() .expireAfterAccess(Duration.ofMinutes(1)) + .scheduler(Scheduler.systemScheduler()) .build() // ...бесконечное хранение пока кто-то все ещё использует текстуру - private val named2DTextures1: Cache = Caffeine.newBuilder() + val named2DTextures1: Cache = Caffeine.newBuilder() .weakValues() + .weakKeys() .build() private val fontShaderPrograms = ArrayList>() @@ -502,44 +506,6 @@ class StarboundClient : Closeable { fun isSameThread() = thread === Thread.currentThread() - fun loadTexture(path: String): GLTexture2D { - ensureSameThread() - - return named2DTextures0.get(path) { - named2DTextures1.get(it) { - val data = Image.get(it) - - if (data == null) { - LOGGER.error("Texture $it is not found!") - missingTexture - } else { - val tex = GLTexture2D(data.width, data.height, when (data.amountOfChannels) { - 1 -> GL_R8 - 3 -> GL_RGB8 - 4 -> GL_RGBA8 - else -> throw IllegalArgumentException("Unknown amount of channels in $it: ${data.amountOfChannels}") - }) - - val fileFormat = when (data.amountOfChannels) { - 1 -> GL_RED - 3 -> GL_RGB - 4 -> GL_RGBA - else -> throw IllegalArgumentException("Unknown amount of channels in $it: ${data.amountOfChannels}") - } - - data.data.thenApplyAsync({ - tex.upload(fileFormat, GL_UNSIGNED_BYTE, it) - - tex.textureMinFilter = GL_NEAREST - tex.textureMagFilter = GL_NEAREST - }, foregroundExecutor) - - tex - } - } - } - } - fun newEBO() = BufferObject.EBO() fun newVBO() = BufferObject.VBO() fun newVAO() = VertexArrayObject() 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 1e6c8285..4df63da4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -7,7 +7,6 @@ import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL45.* import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.Registries -import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.shader.UberShader @@ -16,12 +15,10 @@ import ru.dbotthepony.kstarbound.defs.tile.* import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.TileColor -import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2i import java.time.Duration import java.util.concurrent.Callable -import kotlin.collections.HashMap /** * Хранит в себе программы для отрисовки определённых [TileDefinition] @@ -116,7 +113,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { } val client get() = renderers.client - val texture = def.renderParameters.texture?.imagePath?.value?.let { client.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }} + val texture = def.renderParameters.texture?.image?.texture?.also { it.textureMagFilter = GL_NEAREST } val equalityTester: EqualityRuleTester = when (def) { is TileDefinition -> TileEqualityTester(def) @@ -152,7 +149,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { var mins = piece.texturePosition var maxs = piece.texturePosition + piece.textureSize - if (def.renderParameters.variants != 0 && piece.variantStride != null && piece.texture == null) { + if (def.renderParameters.variants != 0 && piece.variantStride != null && piece.image == null) { val variant = (getter.randomDoubleFor(pos) * def.renderParameters.variants).toInt() mins += piece.variantStride * variant maxs += piece.variantStride * variant @@ -187,11 +184,11 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { ): TestResult { if (matchPiece.test(getter, equalityTester, pos)) { for (renderPiece in matchPiece.pieces) { - if (renderPiece.piece.texture != null) { + if (renderPiece.piece.image != null) { val program = if (isBackground) { - renderers.background(client.loadTexture(renderPiece.piece.texture!!)) + renderers.background(renderPiece.piece.image!!.texture) } else { - renderers.foreground(client.loadTexture(renderPiece.piece.texture!!)) + renderers.foreground(renderPiece.piece.image!!.texture) } tesselateAt( diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt index 4de6c9b8..1fd33cf2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt @@ -82,7 +82,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) { val sprite = path.sprite ?: return - val texture = client.loadTexture(path.imagePath.value!!) + val texture = path.image!!.texture val program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap val mat = transform.map({ it.copy() }, { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt index ce2560ef..0bf1c1fb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt @@ -11,16 +11,22 @@ import com.google.gson.JsonArray import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException +import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap import org.apache.logging.log4j.LogManager +import org.lwjgl.opengl.GL45 import org.lwjgl.stb.STBImage import org.lwjgl.system.MemoryUtil import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITi import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.api.IStarboundFile +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.gl.GLTexture2D +import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.io.stream2STBIO import ru.dbotthepony.kstarbound.util.contains import ru.dbotthepony.kstarbound.util.get @@ -56,6 +62,7 @@ class Image private constructor( private val spritesInternal = Object2ObjectLinkedOpenHashMap() private var dataRef: WeakReference? = null private val lock = ReentrantLock() + //private val _texture = ThreadLocal>() init { if (sprites == null) { @@ -97,6 +104,43 @@ class Image private constructor( } } + val texture: GLTexture2D get() { + //val get = _texture.get()?.get() + val client = StarboundClient.current() + + /*if (get != null) { + // update access time + client.named2DTextures0.getIfPresent(this) + client.named2DTextures1.getIfPresent(this) + return get + }*/ + + val value = client.named2DTextures0.get(this) { + client.named2DTextures1.get(this) { + val (memFormat, fileFormat) = when (amountOfChannels) { + 1 -> GL45.GL_R8 to GL45.GL_RED + 3 -> GL45.GL_RGB8 to GL45.GL_RGB + 4 -> GL45.GL_RGBA8 to GL45.GL_RGBA + else -> throw IllegalArgumentException("Unknown amount of channels in $it: $amountOfChannels") + } + + val tex = GLTexture2D(width, height, memFormat) + + data.thenApplyAsync({ + tex.upload(fileFormat, GL45.GL_UNSIGNED_BYTE, it) + + tex.textureMinFilter = GL45.GL_NEAREST + tex.textureMagFilter = GL45.GL_NEAREST + }, client.foregroundExecutor) + + tex + } + } + + //_texture.set(WeakReference(value)) + return value + } + val size = Vector2i(width, height) val sprites: Map = Collections.unmodifiableMap(this.spritesInternal) val first: Sprite = this.spritesInternal.values.first() @@ -250,7 +294,7 @@ class Image private constructor( } - companion object { + companion object : TypeAdapter() { const val FILL_RATIO = 1 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT) private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) } @@ -317,6 +361,20 @@ class Image private constructor( }.orElse(null) } + override fun write(out: JsonWriter, value: Image?) { + if (value == null) + out.nullValue() + else + out.value(value.path) + } + + override fun read(`in`: JsonReader): Image? { + if (`in`.consumeNull()) + return null + else + return get(`in`.nextString()) + } + private fun generateFakeNames(dimensions: Vector2i): JsonArray { return JsonArray(dimensions.y).also { var stripElem = 0 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt index 51c2880f..b267e7aa 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.world.api.ITileAccess @@ -12,7 +13,7 @@ import ru.dbotthepony.kvector.vector.Vector2i @JsonFactory data class RenderPiece( - val texture: String? = null, + val image: Image? = null, val textureSize: Vector2i, val texturePosition: Vector2i, val colorStride: Vector2i? = null,