Have textures be logically loaded from Image itself

This commit is contained in:
DBotThePony 2023-10-12 08:10:37 +07:00
parent c8488c3565
commit 0b163481e7
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 75 additions and 51 deletions

View File

@ -190,6 +190,8 @@ object Starbound : ISBFileLocator {
registerTypeAdapterFactory(ItemReference.Factory(STRINGS)) registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
registerTypeAdapterFactory(TreasurePoolDefinition.Companion) registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
registerTypeAdapter(Image.Companion)
registerTypeAdapterFactory(with(RegistryReferenceFactory()) { registerTypeAdapterFactory(with(RegistryReferenceFactory()) {
add(Registries.tiles) add(Registries.tiles)
add(Registries.tileModifiers) add(Registries.tileModifiers)

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client
import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.BufferUtils import org.lwjgl.BufferUtils
import org.lwjgl.glfw.Callbacks import org.lwjgl.glfw.Callbacks
@ -73,6 +74,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.Duration import java.time.Duration
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
import java.util.concurrent.ForkJoinWorkerThread import java.util.concurrent.ForkJoinWorkerThread
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -282,13 +284,15 @@ class StarboundClient : Closeable {
val stack = Matrix3fStack() val stack = Matrix3fStack()
// минимальное время хранения 5 минут и... // минимальное время хранения 5 минут и...
private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder() val named2DTextures0: Cache<Image, GLTexture2D> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(1)) .expireAfterAccess(Duration.ofMinutes(1))
.scheduler(Scheduler.systemScheduler())
.build() .build()
// ...бесконечное хранение пока кто-то все ещё использует текстуру // ...бесконечное хранение пока кто-то все ещё использует текстуру
private val named2DTextures1: Cache<String, GLTexture2D> = Caffeine.newBuilder() val named2DTextures1: Cache<Image, GLTexture2D> = Caffeine.newBuilder()
.weakValues() .weakValues()
.weakKeys()
.build() .build()
private val fontShaderPrograms = ArrayList<WeakReference<FontProgram>>() private val fontShaderPrograms = ArrayList<WeakReference<FontProgram>>()
@ -502,44 +506,6 @@ class StarboundClient : Closeable {
fun isSameThread() = thread === Thread.currentThread() 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 newEBO() = BufferObject.EBO()
fun newVBO() = BufferObject.VBO() fun newVBO() = BufferObject.VBO()
fun newVAO() = VertexArrayObject() fun newVAO() = VertexArrayObject()

View File

@ -7,7 +7,6 @@ import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GL45.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.gl.shader.UberShader 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.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import java.time.Duration import java.time.Duration
import java.util.concurrent.Callable import java.util.concurrent.Callable
import kotlin.collections.HashMap
/** /**
* Хранит в себе программы для отрисовки определённых [TileDefinition] * Хранит в себе программы для отрисовки определённых [TileDefinition]
@ -116,7 +113,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
} }
val client get() = renderers.client 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) { val equalityTester: EqualityRuleTester = when (def) {
is TileDefinition -> TileEqualityTester(def) is TileDefinition -> TileEqualityTester(def)
@ -152,7 +149,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
var mins = piece.texturePosition var mins = piece.texturePosition
var maxs = piece.texturePosition + piece.textureSize 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() val variant = (getter.randomDoubleFor(pos) * def.renderParameters.variants).toInt()
mins += piece.variantStride * variant mins += piece.variantStride * variant
maxs += piece.variantStride * variant maxs += piece.variantStride * variant
@ -187,11 +184,11 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
): TestResult { ): TestResult {
if (matchPiece.test(getter, equalityTester, pos)) { if (matchPiece.test(getter, equalityTester, pos)) {
for (renderPiece in matchPiece.pieces) { for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) { if (renderPiece.piece.image != null) {
val program = if (isBackground) { val program = if (isBackground) {
renderers.background(client.loadTexture(renderPiece.piece.texture!!)) renderers.background(renderPiece.piece.image!!.texture)
} else { } else {
renderers.foreground(client.loadTexture(renderPiece.piece.texture!!)) renderers.foreground(renderPiece.piece.image!!.texture)
} }
tesselateAt( tesselateAt(

View File

@ -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) { override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
val sprite = path.sprite ?: return 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 program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap
val mat = transform.map({ it.copy() }, { val mat = transform.map({ it.copy() }, {

View File

@ -11,16 +11,22 @@ import com.google.gson.JsonArray
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader 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.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL45
import org.lwjgl.stb.STBImage import org.lwjgl.stb.STBImage
import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITi import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITi
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.api.IStarboundFile 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.io.stream2STBIO
import ru.dbotthepony.kstarbound.util.contains import ru.dbotthepony.kstarbound.util.contains
import ru.dbotthepony.kstarbound.util.get import ru.dbotthepony.kstarbound.util.get
@ -56,6 +62,7 @@ class Image private constructor(
private val spritesInternal = Object2ObjectLinkedOpenHashMap<String, Sprite>() private val spritesInternal = Object2ObjectLinkedOpenHashMap<String, Sprite>()
private var dataRef: WeakReference<ByteBuffer>? = null private var dataRef: WeakReference<ByteBuffer>? = null
private val lock = ReentrantLock() private val lock = ReentrantLock()
//private val _texture = ThreadLocal<WeakReference<GLTexture2D>>()
init { init {
if (sprites == null) { 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 size = Vector2i(width, height)
val sprites: Map<String, Sprite> = Collections.unmodifiableMap(this.spritesInternal) val sprites: Map<String, Sprite> = Collections.unmodifiableMap(this.spritesInternal)
val first: Sprite = this.spritesInternal.values.first() val first: Sprite = this.spritesInternal.values.first()
@ -250,7 +294,7 @@ class Image private constructor(
} }
companion object { companion object : TypeAdapter<Image>() {
const val FILL_RATIO = 1 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT) const val FILL_RATIO = 1 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT)
private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) } private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
@ -317,6 +361,20 @@ class Image private constructor(
}.orElse(null) }.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 { private fun generateFakeNames(dimensions: Vector2i): JsonArray {
return JsonArray(dimensions.y).also { return JsonArray(dimensions.y).also {
var stripElem = 0 var stripElem = 0

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager 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.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.ITileAccess
@ -12,7 +13,7 @@ import ru.dbotthepony.kvector.vector.Vector2i
@JsonFactory @JsonFactory
data class RenderPiece( data class RenderPiece(
val texture: String? = null, val image: Image? = null,
val textureSize: Vector2i, val textureSize: Vector2i,
val texturePosition: Vector2i, val texturePosition: Vector2i,
val colorStride: Vector2i? = null, val colorStride: Vector2i? = null,