make opengl texture array implementation lighter

Most of the functionality is actually not opengl specific, so abstract it
This commit is contained in:
Moritz Zwerger 2023-11-25 22:12:28 +01:00
parent 31225196ee
commit 35a9a08880
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
33 changed files with 179 additions and 161 deletions

View File

@ -34,7 +34,7 @@ class BakedFaceTest {
private fun texture(): Texture { private fun texture(): Texture {
val manager = BakedModelTestUtil.createTextureManager(texture) val manager = BakedModelTestUtil.createTextureManager(texture)
return manager.staticTextures.createTexture(texture.toResourceLocation()) return manager.staticTextures.create(texture.toResourceLocation())
} }
private fun singleMesh(): ChunkMesh { private fun singleMesh(): ChunkMesh {

View File

@ -13,17 +13,22 @@
package de.bixilon.minosoft.gui.rendering.system.dummy.texture package de.bixilon.minosoft.gui.rendering.system.dummy.texture
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.latch.AbstractLatch import de.bixilon.kutil.latch.AbstractLatch
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.StaticTextureArray import de.bixilon.minosoft.gui.rendering.system.base.texture.array.StaticTextureArray
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.Texture
import java.util.concurrent.atomic.AtomicInteger
class DummyStaticTextureArray(context: RenderContext) : StaticTextureArray(context, false, 0) { class DummyStaticTextureArray(context: RenderContext) : StaticTextureArray(context, false, 0) {
override fun load(latch: AbstractLatch) { override fun load(animationIndex: AtomicInteger, textures: Collection<Texture>) {
for (texture in this.named.values) { for (texture in textures) {
(texture as DummyTexture).state = TextureStates.LOADED if (texture !is DummyTexture) continue
texture.state = TextureStates.LOADED
} }
} }
@ -31,6 +36,12 @@ class DummyStaticTextureArray(context: RenderContext) : StaticTextureArray(conte
animator.init() animator.init()
} }
override fun create(resourceLocation: ResourceLocation, mipmaps: Boolean, properties: Boolean, factory: (mipmaps: Int) -> Texture): Texture {
return super.create(resourceLocation, mipmaps, properties) { DummyTexture() }
}
override fun findResolution(size: Vec2i) = size
override fun activate() = Unit override fun activate() = Unit
override fun use(shader: NativeShader, name: String) = Unit override fun use(shader: NativeShader, name: String) = Unit
} }

View File

@ -26,7 +26,7 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.TextureRend
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
class DummyTexture : Texture { class DummyTexture : Texture {
override var array = TextureArrayProperties(Vec2(), 1, Vec2()) override var array = TextureArrayProperties(Vec2(), 1, 1.0f)
override var state: TextureStates = TextureStates.DECLARED override var state: TextureStates = TextureStates.DECLARED
override var size: Vec2i = Vec2i(1, 1) override var size: Vec2i = Vec2i(1, 1)
override val transparency: TextureTransparencies get() = TextureTransparencies.OPAQUE override val transparency: TextureTransparencies get() = TextureTransparencies.OPAQUE

View File

@ -59,7 +59,7 @@ class WorldBorderRenderer(
shader.native.defines["MAX_DISTANCE"] = MAX_DISTANCE shader.native.defines["MAX_DISTANCE"] = MAX_DISTANCE
shader.load() shader.load()
texture = context.textures.staticTextures.createTexture(TEXTURE) texture = context.textures.staticTextures.create(TEXTURE)
context.camera.offset::offset.observe(this) { reload = true } context.camera.offset::offset.observe(this) { reload = true }
} }

View File

@ -44,7 +44,7 @@ class DoubleChestRenderer(
private fun register(loader: ModelLoader, name: ResourceLocation, texture: ResourceLocation) { private fun register(loader: ModelLoader, name: ResourceLocation, texture: ResourceLocation) {
val static = loader.context.textures.staticTextures val static = loader.context.textures.staticTextures
val override = mapOf(TEXTURE to static.createTexture(texture)) val override = mapOf(TEXTURE to static.create(texture))
loader.skeletal.register(name, MODEL, override) loader.skeletal.register(name, MODEL, override)
} }
@ -52,8 +52,8 @@ class DoubleChestRenderer(
if (textures.size != 2) throw IllegalStateException("Textures must be left and right!") if (textures.size != 2) throw IllegalStateException("Textures must be left and right!")
val static = loader.context.textures.staticTextures val static = loader.context.textures.staticTextures
val override = mapOf( val override = mapOf(
TEXTURE_5[0] to static.createTexture(textures[0]), TEXTURE_5[0] to static.create(textures[0]),
TEXTURE_5[1] to static.createTexture(textures[1]), TEXTURE_5[1] to static.create(textures[1]),
) )
loader.skeletal.register(name, MODEL_5, override) loader.skeletal.register(name, MODEL_5, override)
} }

View File

@ -41,7 +41,7 @@ class SingleChestRenderer(
private val named = minecraft("chest") private val named = minecraft("chest")
fun register(loader: ModelLoader, name: ResourceLocation, texture: ResourceLocation) { fun register(loader: ModelLoader, name: ResourceLocation, texture: ResourceLocation) {
val texture = loader.context.textures.staticTextures.createTexture(texture) val texture = loader.context.textures.staticTextures.create(texture)
val model = if (loader.packFormat < 5) MODEL else MODEL_5 val model = if (loader.packFormat < 5) MODEL else MODEL_5
loader.skeletal.register(name, model, mapOf(named to texture)) loader.skeletal.register(name, model, mapOf(named to texture))
} }

View File

@ -95,7 +95,7 @@ class ShulkerBoxRenderer(
} }
private fun load(name: ResourceLocation, texture: ResourceLocation, loader: ModelLoader) { private fun load(name: ResourceLocation, texture: ResourceLocation, loader: ModelLoader) {
val texture = loader.context.textures.staticTextures.createTexture(texture) val texture = loader.context.textures.staticTextures.create(texture)
loader.skeletal.register(name, TEMPLATE, override = mapOf(this.named to texture)) loader.skeletal.register(name, TEMPLATE, override = mapOf(this.named to texture))
} }
} }

View File

@ -27,7 +27,8 @@ class BitmapCodeRenderer(
) : AscentedCodePointRenderer { ) : AscentedCodePointRenderer {
fun updateArray() { fun updateArray() {
uvStart *= texture.array.uvEnd val end = texture.array.uvEnd ?: return
uvEnd *= texture.array.uvEnd uvStart *= end
uvEnd *= end
} }
} }

View File

@ -78,7 +78,7 @@ class BitmapFontType(
private fun load(file: ResourceLocation, height: Int, ascent: Int, chars: List<String>, context: RenderContext): BitmapFontType? { private fun load(file: ResourceLocation, height: Int, ascent: Int, chars: List<String>, context: RenderContext): BitmapFontType? {
if (chars.isEmpty() || height <= 0) return null if (chars.isEmpty() || height <= 0) return null
val texture = context.textures.staticTextures.createTexture(file, mipmaps = false, properties = false) val texture = context.textures.staticTextures.create(file, mipmaps = false, properties = false)
texture.load(context) // force load it, we need to calculate the width of every char texture.load(context) // force load it, we need to calculate the width of every char
return load(texture, texture.size.y / chars.size, ascent, chars.codePoints()) return load(texture, texture.size.y / chars.size, ascent, chars.codePoints())

View File

@ -19,14 +19,15 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.Texture
class UnicodeCodeRenderer( class UnicodeCodeRenderer(
override val texture: Texture, override val texture: Texture,
override var uvStart: Vec2, override val uvStart: Vec2,
override var uvEnd: Vec2, override val uvEnd: Vec2,
override val width: Float, override val width: Float,
) : RasterizedCodePointRenderer { ) : RasterizedCodePointRenderer {
fun updateArray() { fun updateArray() {
uvStart = uvStart * texture.array.uvEnd val end = texture.array.uvEnd ?: return
uvEnd = uvEnd * texture.array.uvEnd uvStart *= end
uvEnd *= end
} }
} }

View File

@ -87,7 +87,7 @@ class LegacyUnicodeFontType(
sizes.skip(PAGE_SIZE.toLong()) sizes.skip(PAGE_SIZE.toLong())
return return
} }
val texture = textures.createTexture(textureFile, mipmaps = false, properties = false) val texture = textures.create(textureFile, mipmaps = false, properties = false)
loadPage(pageId, texture, chars, sizes) loadPage(pageId, texture, chars, sizes)
} }

View File

@ -13,11 +13,11 @@
package de.bixilon.minosoft.gui.rendering.font.types.unicode.unihex package de.bixilon.minosoft.gui.rendering.font.types.unicode.unihex
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.gui.rendering.font.renderer.code.CodePointRenderer import de.bixilon.minosoft.gui.rendering.font.renderer.code.CodePointRenderer
import de.bixilon.minosoft.gui.rendering.font.types.empty.EmptyCodeRenderer import de.bixilon.minosoft.gui.rendering.font.types.empty.EmptyCodeRenderer
import de.bixilon.minosoft.gui.rendering.font.types.unicode.unihex.UnifontTexture.Companion.isPixelSet import de.bixilon.minosoft.gui.rendering.font.types.unicode.unihex.UnifontTexture.Companion.isPixelSet
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.StaticTextureArray import de.bixilon.minosoft.gui.rendering.system.base.texture.array.StaticTextureArray
import de.bixilon.minosoft.gui.rendering.system.opengl.texture.OpenGLTextureArray.Companion.TEXTURE_RESOLUTION_ID_MAP
class UnifontRasterizer( class UnifontRasterizer(
private val array: StaticTextureArray, private val array: StaticTextureArray,
@ -54,15 +54,8 @@ class UnifontRasterizer(
} }
private fun calculateRows(width: Int): Int { private fun calculateRows(width: Int): Int {
var previous = TEXTURE_RESOLUTION_ID_MAP.last() val size = array.findResolution(Vec2i(width, HEIGHT))
for (index in TEXTURE_RESOLUTION_ID_MAP.size - 1 downTo 0) { return size.y / HEIGHT
val resolution = TEXTURE_RESOLUTION_ID_MAP[index]
val size = resolution * resolution / HEIGHT
if (width >= size) return previous / HEIGHT
previous = resolution
}
return 1
} }
private fun createTexture(): UnifontTexture { private fun createTexture(): UnifontTexture {

View File

@ -31,7 +31,7 @@ class FireOverlay(
private val config = context.connection.profiles.rendering.overlay.fire private val config = context.connection.profiles.rendering.overlay.fire
private val player = context.connection.player private val player = context.connection.player
private val shader = context.shaders.genericTexture2dShader private val shader = context.shaders.genericTexture2dShader
private var texture: Texture = context.textures.staticTextures.createTexture("block/fire_1".toResourceLocation().texture()) private var texture: Texture = context.textures.staticTextures.create("block/fire_1".toResourceLocation().texture())
private val lava = context.connection.registries.fluid[LavaFluid] private val lava = context.connection.registries.fluid[LavaFluid]
override val render: Boolean override val render: Boolean
get() { get() {

View File

@ -23,7 +23,7 @@ import de.bixilon.minosoft.util.KUtil.toResourceLocation
class PowderSnowOverlay(context: RenderContext) : SimpleOverlay(context) { class PowderSnowOverlay(context: RenderContext) : SimpleOverlay(context) {
private val config = context.connection.profiles.rendering.overlay private val config = context.connection.profiles.rendering.overlay
override val texture: Texture = context.textures.staticTextures.createTexture(OVERLAY_TEXTURE) override val texture: Texture = context.textures.staticTextures.create(OVERLAY_TEXTURE)
private val strength = FloatAverage(1L * 1000000000L, 0.0f) private val strength = FloatAverage(1L * 1000000000L, 0.0f)
override var render: Boolean = false override var render: Boolean = false
get() = config.powderSnow && field get() = config.powderSnow && field

View File

@ -23,7 +23,7 @@ import de.bixilon.minosoft.util.KUtil.toResourceLocation
class PumpkinOverlay(context: RenderContext) : FirstPersonOverlay(context) { class PumpkinOverlay(context: RenderContext) : FirstPersonOverlay(context) {
private val config = context.connection.profiles.rendering.overlay private val config = context.connection.profiles.rendering.overlay
override val texture: Texture = context.textures.staticTextures.createTexture(OVERLAY_TEXTURE) override val texture: Texture = context.textures.staticTextures.create(OVERLAY_TEXTURE)
override val render: Boolean override val render: Boolean
get() { get() {
if (!config.pumpkin) { if (!config.pumpkin) {

View File

@ -24,7 +24,7 @@ import de.bixilon.minosoft.util.KUtil.toResourceLocation
class WaterOverlay(context: RenderContext) : SimpleOverlay(context) { class WaterOverlay(context: RenderContext) : SimpleOverlay(context) {
private val player = context.connection.player private val player = context.connection.player
override val texture: Texture = context.textures.staticTextures.createTexture("minecraft:misc/underwater".toResourceLocation().texture()) override val texture: Texture = context.textures.staticTextures.create("minecraft:misc/underwater".toResourceLocation().texture())
override val render: Boolean override val render: Boolean
get() = player.gamemode != Gamemodes.SPECTATOR && player.physics.submersion.eye is WaterFluid get() = player.gamemode != Gamemodes.SPECTATOR && player.physics.submersion.eye is WaterFluid

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.util.KUtil.toResourceLocation
class WorldBorderOverlay(context: RenderContext) : SimpleOverlay(context) { class WorldBorderOverlay(context: RenderContext) : SimpleOverlay(context) {
private val config = context.connection.profiles.rendering.overlay private val config = context.connection.profiles.rendering.overlay
override val texture: Texture = context.textures.staticTextures.createTexture(OVERLAY_TEXTURE) override val texture: Texture = context.textures.staticTextures.create(OVERLAY_TEXTURE)
override val render: Boolean override val render: Boolean
get() = config.worldBorder && context.connection.world.border.isOutside(context.connection.player.physics.position) get() = config.worldBorder && context.connection.world.border.isOutside(context.connection.player.physics.position)

View File

@ -33,8 +33,8 @@ import java.util.*
class WeatherOverlay(private val context: RenderContext) : Overlay { class WeatherOverlay(private val context: RenderContext) : Overlay {
private val world = context.connection.world private val world = context.connection.world
private val config = context.connection.profiles.rendering.overlay.weather private val config = context.connection.profiles.rendering.overlay.weather
private val rain = context.textures.staticTextures.createTexture(RAIN) private val rain = context.textures.staticTextures.create(RAIN)
private val snow = context.textures.staticTextures.createTexture(SNOW) private val snow = context.textures.staticTextures.create(SNOW)
private val precipitation get() = context.connection.player.physics.positionInfo.biome?.precipitation private val precipitation get() = context.connection.player.physics.positionInfo.biome?.precipitation
override val render: Boolean override val render: Boolean
get() = world.dimension.effects.weather && world.weather.raining && when (precipitation) { // ToDo: Check if exposed to the sky get() = world.dimension.effects.weather && world.weather.raining && when (precipitation) { // ToDo: Check if exposed to the sky
@ -70,7 +70,7 @@ class WeatherOverlay(private val context: RenderContext) : Overlay {
val offsetMultiplicator = random.nextFloat(0.8f, 1.2f) val offsetMultiplicator = random.nextFloat(0.8f, 1.2f)
val alpha = random.nextFloat(0.8f, 1.0f) val alpha = random.nextFloat(0.8f, 1.0f)
mesh.addZQuad( mesh.addZQuad(
Vec2(offset, 0), OVERLAY_Z, Vec2(offset + step, windowSize.y), Vec2(0.0f), texture.array.uvEnd Vec2(offset, 0), OVERLAY_Z, Vec2(offset + step, windowSize.y), Vec2(0.0f), texture.array.uvEnd ?: Vec2(1.0f)
) { position, uv -> ) { position, uv ->
val transformed = Vec2() val transformed = Vec2()
transformed.x = position.x / (windowSize.x / 2) - 1.0f transformed.x = position.x / (windowSize.x / 2) - 1.0f

View File

@ -44,14 +44,14 @@ data class BlockModel(
fun createTexture(name: String, textures: TextureManager): Texture? { fun createTexture(name: String, textures: TextureManager): Texture? {
if (!name.startsWith("#")) { if (!name.startsWith("#")) {
return textures.staticTextures.createTexture(name.toResourceLocation()) return textures.staticTextures.create(name.toResourceLocation())
} }
val texture = this.textures?.get(name.substring(1)) val texture = this.textures?.get(name.substring(1))
if (texture == null || texture !is ResourceLocation) { if (texture == null || texture !is ResourceLocation) {
return null return null
} }
return textures.staticTextures.createTexture(texture) return textures.staticTextures.create(texture)
} }
fun getOrNullTexture(name: String, textures: TextureManager): Texture? { fun getOrNullTexture(name: String, textures: TextureManager): Texture? {

View File

@ -27,8 +27,8 @@ class LavaFluidModel : FluidModel {
override val transparency = TextureTransparencies.OPAQUE// TODO: from texture override val transparency = TextureTransparencies.OPAQUE// TODO: from texture
override fun load(context: RenderContext) { override fun load(context: RenderContext) {
still = context.textures.staticTextures.createTexture(context.models.block.fixTexturePath(STILL).texture()) still = context.textures.staticTextures.create(context.models.block.fixTexturePath(STILL).texture())
flowing = context.textures.staticTextures.createTexture(context.models.block.fixTexturePath(FLOWING).texture()) flowing = context.textures.staticTextures.create(context.models.block.fixTexturePath(FLOWING).texture())
} }
companion object { companion object {

View File

@ -31,8 +31,8 @@ class WaterFluidModel : FluidModel {
override val transparency = TextureTransparencies.TRANSLUCENT// TODO: from texture override val transparency = TextureTransparencies.TRANSLUCENT// TODO: from texture
override fun load(context: RenderContext) { override fun load(context: RenderContext) {
still = context.textures.staticTextures.createTexture(context.models.block.fixTexturePath(STILL).texture()) still = context.textures.staticTextures.create(context.models.block.fixTexturePath(STILL).texture())
flowing = context.textures.staticTextures.createTexture(context.models.block.fixTexturePath(FLOWING).texture()) flowing = context.textures.staticTextures.create(context.models.block.fixTexturePath(FLOWING).texture())
} }
companion object { companion object {

View File

@ -32,7 +32,7 @@ class ItemModel(
if (this.textures == null) return null if (this.textures == null) return null
val texture = this.textures["layer0", "particle"]?.toResourceLocation()?.texture() ?: return null val texture = this.textures["layer0", "particle"]?.toResourceLocation()?.texture() ?: return null
return ItemModelPrototype(textures.staticTextures.createTexture(texture)) return ItemModelPrototype(textures.staticTextures.create(texture))
} }
companion object { companion object {

View File

@ -119,7 +119,7 @@ class ParticleRenderer(
translucentMesh.load() translucentMesh.load()
for (particle in connection.registries.particleType) { for (particle in connection.registries.particleType) {
for (resourceLocation in particle.textures) { for (resourceLocation in particle.textures) {
context.textures.staticTextures.createTexture(resourceLocation) context.textures.staticTextures.create(resourceLocation)
} }
} }

View File

@ -42,7 +42,7 @@ data class SkeletalModel(
if (name in skip) continue if (name in skip) continue
val file = name.texture() val file = name.texture()
if (file in skip) continue if (file in skip) continue
val texture = context.textures.staticTextures.createTexture(file) val texture = context.textures.staticTextures.create(file)
this.loadedTextures[name] = SkeletalTextureInstance(properties, texture) this.loadedTextures[name] = SkeletalTextureInstance(properties, texture)
} }
} }

View File

@ -85,7 +85,7 @@ class SkyboxRenderer(
override fun init() { override fun init() {
for (properties in DefaultDimensionEffects) { for (properties in DefaultDimensionEffects) {
val texture = properties.fixedTexture ?: continue val texture = properties.fixedTexture ?: continue
textureCache[texture] = sky.context.textures.staticTextures.createTexture(texture) textureCache[texture] = sky.context.textures.staticTextures.create(texture)
} }
} }

View File

@ -29,13 +29,14 @@ import java.util.*
class MoonRenderer( class MoonRenderer(
sky: SkyRenderer, sky: SkyRenderer,
) : PlanetRenderer(sky) { ) : PlanetRenderer(sky) {
override val texture = sky.context.textures.staticTextures.createTexture(MOON_PHASES) override val texture = sky.context.textures.staticTextures.create(MOON_PHASES)
private var phase = MoonPhases.FULL_MOON private var phase = MoonPhases.FULL_MOON
private fun updateUV(phases: MoonPhases) { private fun updateUV(phases: MoonPhases) {
val coordinates = PHASE_UV[phases.ordinal] val coordinates = PHASE_UV[phases.ordinal]
uvStart = Vec2(1.0f / 4 * coordinates.x, 1.0f / 2 * coordinates.y) * texture.array.uvEnd val end = texture.array.uvEnd ?: Vec2i(1.0f)
uvEnd = Vec2(1.0f / 4 * (coordinates.x + 1), 1.0f / 2 * (coordinates.y + 1)) * texture.array.uvEnd uvStart = Vec2(1.0f / 4 * coordinates.x, 1.0f / 2 * coordinates.y) * end
uvEnd = Vec2(1.0f / 4 * (coordinates.x + 1), 1.0f / 2 * (coordinates.y + 1)) * end
meshInvalid = true meshInvalid = true
} }

View File

@ -27,7 +27,7 @@ import kotlin.math.pow
class SunRenderer( class SunRenderer(
sky: SkyRenderer, sky: SkyRenderer,
) : PlanetRenderer(sky) { ) : PlanetRenderer(sky) {
override val texture = sky.context.textures.staticTextures.createTexture(SUN) override val texture = sky.context.textures.staticTextures.create(SUN)
public override fun calculateAngle(): Float { public override fun calculateAngle(): Float {
val time = sky.context.connection.world.time val time = sky.context.connection.world.time

View File

@ -42,8 +42,8 @@ abstract class TextureManager {
if (this::debugTexture.isInitialized) { if (this::debugTexture.isInitialized) {
throw IllegalStateException("Already initialized!") throw IllegalStateException("Already initialized!")
} }
debugTexture = staticTextures.createTexture(RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION) debugTexture = staticTextures.create(RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION)
whiteTexture = CodeTexturePart(texture = staticTextures.createTexture(minosoft("white").texture(), mipmaps = false), uvStart = Vec2(0.0f, 0.0f), uvEnd = Vec2(0.001f, 0.001f), size = Vec2i(16, 16)) whiteTexture = CodeTexturePart(texture = staticTextures.create(minosoft("white").texture(), mipmaps = false), uvStart = Vec2(0.0f, 0.0f), uvEnd = Vec2(0.001f, 0.001f), size = Vec2i(16, 16))
} }
fun initializeSkins(connection: PlayConnection) { fun initializeSkins(connection: PlayConnection) {

View File

@ -13,10 +13,14 @@
package de.bixilon.minosoft.gui.rendering.system.base.texture.array package de.bixilon.minosoft.gui.rendering.system.base.texture.array
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
import de.bixilon.kutil.concurrent.pool.ThreadPool
import de.bixilon.kutil.concurrent.pool.runnable.ForcePooledRunnable import de.bixilon.kutil.concurrent.pool.runnable.ForcePooledRunnable
import de.bixilon.kutil.concurrent.pool.runnable.SimplePoolRunnable
import de.bixilon.kutil.latch.AbstractLatch import de.bixilon.kutil.latch.AbstractLatch
import de.bixilon.kutil.latch.AbstractLatch.Companion.child
import de.bixilon.minosoft.assets.util.InputStreamUtil.readAsString import de.bixilon.minosoft.assets.util.InputStreamUtil.readAsString
import de.bixilon.minosoft.data.registries.identified.ResourceLocation import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.RenderContext
@ -28,6 +32,7 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.file.PNGTex
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.json.Jackson import de.bixilon.minosoft.util.json.Jackson
import java.util.concurrent.atomic.AtomicInteger
abstract class StaticTextureArray( abstract class StaticTextureArray(
val context: RenderContext, val context: RenderContext,
@ -50,10 +55,9 @@ abstract class StaticTextureArray(
return texture return texture
} }
operator fun plusAssign(texture: Texture) = pushTexture(texture) operator fun plusAssign(texture: Texture) = push(texture)
fun push(texture: Texture) {
fun pushTexture(texture: Texture) {
lock.lock() lock.lock()
other += texture other += texture
lock.unlock() lock.unlock()
@ -62,7 +66,7 @@ abstract class StaticTextureArray(
} }
} }
fun createTexture(resourceLocation: ResourceLocation, mipmaps: Boolean = true, properties: Boolean = true, factory: (mipmaps: Int) -> Texture = { PNGTexture(resourceLocation, mipmaps = it) }): Texture { open fun create(resourceLocation: ResourceLocation, mipmaps: Boolean = true, properties: Boolean = true, factory: (mipmaps: Int) -> Texture = { PNGTexture(resourceLocation, mipmaps = it) }): Texture {
lock.lock() lock.lock()
named[resourceLocation]?.let { lock.unlock(); return it } named[resourceLocation]?.let { lock.unlock(); return it }
@ -92,6 +96,30 @@ abstract class StaticTextureArray(
return null return null
} }
abstract fun findResolution(size: Vec2i): Vec2i
abstract fun load(latch: AbstractLatch)
private fun load(latch: AbstractLatch, textures: Collection<Texture>) {
for (texture in textures) {
if (texture.state != TextureStates.DECLARED) continue
latch.inc()
DefaultThreadPool += SimplePoolRunnable(ThreadPool.HIGH) { texture.load(context); latch.dec() }
}
}
protected abstract fun load(animationIndex: AtomicInteger, textures: Collection<Texture>)
fun load(latch: AbstractLatch) {
val latch = latch.child(0)
load(latch, named.values)
load(latch, other)
latch.await()
val animationIndex = AtomicInteger()
load(animationIndex, named.values)
load(animationIndex, other)
state = TextureArrayStates.LOADED
}
} }

View File

@ -16,7 +16,7 @@ package de.bixilon.minosoft.gui.rendering.system.base.texture.array
import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kotlinglm.vec2.Vec2
data class TextureArrayProperties( data class TextureArrayProperties(
val uvEnd: Vec2, val uvEnd: Vec2?,
val size: Int, val size: Int,
val pixel: Vec2, val pixel: Float,
) )

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2021 Moritz Zwerger * Copyright (C) 2020-2023 Moritz Zwerger
* *
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* *
@ -15,8 +15,8 @@ package de.bixilon.minosoft.gui.rendering.system.base.texture.array
enum class TextureArrayStates { enum class TextureArrayStates {
DECLARED, DECLARED,
PRE_LOADED,
LOADED, LOADED,
UPLOADED,
UNLOADED, UNLOADED,
; ;
} }

View File

@ -25,6 +25,7 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.memory.Memo
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
@Deprecated("Slow, because of synchronized loading of mcmeta")
class SpriteTexture(private val original: Texture) : Texture { class SpriteTexture(private val original: Texture) : Texture {
override var array: TextureArrayProperties by original::array override var array: TextureArrayProperties by original::array
override var properties: ImageProperties by original::properties override var properties: ImageProperties by original::properties

View File

@ -14,15 +14,11 @@
package de.bixilon.minosoft.gui.rendering.system.opengl.texture package de.bixilon.minosoft.gui.rendering.system.opengl.texture
import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.cast.CastUtil.unsafeCast import de.bixilon.kutil.cast.CastUtil.unsafeCast
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
import de.bixilon.kutil.concurrent.pool.ThreadPool
import de.bixilon.kutil.concurrent.pool.runnable.SimplePoolRunnable
import de.bixilon.kutil.latch.AbstractLatch import de.bixilon.kutil.latch.AbstractLatch
import de.bixilon.kutil.latch.SimpleLatch
import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.StaticTextureArray import de.bixilon.minosoft.gui.rendering.system.base.texture.array.StaticTextureArray
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.TextureArrayProperties import de.bixilon.minosoft.gui.rendering.system.base.texture.array.TextureArrayProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.TextureArrayStates import de.bixilon.minosoft.gui.rendering.system.base.texture.array.TextureArrayStates
@ -46,92 +42,17 @@ class OpenGLTextureArray(
async: Boolean, async: Boolean,
mipmaps: Int, mipmaps: Int,
) : StaticTextureArray(context, async, mipmaps) { ) : StaticTextureArray(context, async, mipmaps) {
private var textureIds = IntArray(TEXTURE_RESOLUTION_ID_MAP.size) { -1 } private var handles = IntArray(RESOLUTIONS.size) { -1 }
private val texturesByResolution = Array<MutableList<Texture>>(TEXTURE_RESOLUTION_ID_MAP.size) { mutableListOf() } private val resolution = Array<MutableList<Texture>>(RESOLUTIONS.size) { mutableListOf() }
private val lastTextureId = IntArray(TEXTURE_RESOLUTION_ID_MAP.size) private val lastTextureId = IntArray(RESOLUTIONS.size)
init { init {
context.system.unsafeCast<OpenGLRenderSystem>().textureBindingIndex += TEXTURE_RESOLUTION_ID_MAP.size context.system.unsafeCast<OpenGLRenderSystem>().textureBindingIndex += RESOLUTIONS.size
} }
private fun preLoad(latch: AbstractLatch, textures: Collection<Texture>) { private fun upload(resolution: Int, textures: List<Texture>): Int {
for (texture in textures) {
if (texture.state != TextureStates.DECLARED) {
latch.dec()
continue
}
DefaultThreadPool += SimplePoolRunnable(ThreadPool.HIGH) { texture.load(context); latch.dec() }
}
}
private fun preLoad(animationIndex: AtomicInteger, textures: Collection<Texture>) {
for (texture in textures) {
check(texture.size.x <= TEXTURE_MAX_RESOLUTION) { "Texture's width exceeds $TEXTURE_MAX_RESOLUTION (${texture.size.x})" }
check(texture.size.y <= TEXTURE_MAX_RESOLUTION) { "Texture's height exceeds $TEXTURE_MAX_RESOLUTION (${texture.size.y})" }
var arrayId = -1
var arrayResolution = -1
for (i in TEXTURE_RESOLUTION_ID_MAP.indices) {
arrayResolution = TEXTURE_RESOLUTION_ID_MAP[i]
if (texture.size.x <= arrayResolution && texture.size.y <= arrayResolution) {
arrayId = i
break
}
}
val uvEnd: Vec2? = if (texture.size.x == arrayResolution && texture.size.y == arrayResolution) {
null
} else {
Vec2(texture.size) / arrayResolution
}
val singlePixelSize = Vec2(1.0f) / arrayResolution
val array = TextureArrayProperties(uvEnd ?: Vec2(1.0f, 1.0f), arrayResolution, singlePixelSize)
if (texture is SpriteTexture) {
val animationIndex = animationIndex.getAndIncrement()
val animation = TextureAnimation(texture)
animator.animations += animation
texture.renderData = OpenGLTextureData(-1, -1, uvEnd, animationIndex)
for (split in texture.splitTextures) {
split.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, animationIndex)
split.array = array
texturesByResolution[arrayId] += split
}
for (frame in texture.properties.animation!!.frames) {
frame.texture = texture.splitTextures[frame.index]
}
} else {
texturesByResolution[arrayId] += texture
texture.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, -1)
texture.array = array
}
}
}
@Synchronized
override fun load(latch: AbstractLatch) {
if (state == TextureArrayStates.LOADED || state == TextureArrayStates.PRE_LOADED) {
return
}
val preLoadLatch = SimpleLatch(named.size + other.size)
preLoad(preLoadLatch, named.values)
preLoad(preLoadLatch, other)
preLoadLatch.await()
val animationIndex = AtomicInteger()
preLoad(animationIndex, named.values)
preLoad(animationIndex, other)
state = TextureArrayStates.PRE_LOADED
}
private fun loadSingleArray(resolution: Int, textures: List<Texture>): Int {
val textureId = OpenGLTextureUtil.createTextureArray(mipmaps) val textureId = OpenGLTextureUtil.createTextureArray(mipmaps)
for (level in 0..mipmaps) { for (level in 0..mipmaps) {
@ -154,22 +75,20 @@ class OpenGLTextureArray(
override fun upload(latch: AbstractLatch?) { override fun upload(latch: AbstractLatch?) {
var totalLayers = 0 var total = 0
for ((index, textures) in texturesByResolution.withIndex()) { for ((index, textures) in resolution.withIndex()) {
if (textures.isEmpty()) { if (textures.isEmpty()) continue
continue handles[index] = upload(RESOLUTIONS[index], textures)
} total += textures.size
textureIds[index] = loadSingleArray(TEXTURE_RESOLUTION_ID_MAP[index], textures)
totalLayers += textures.size
} }
Log.log(LogMessageType.RENDERING, LogLevels.VERBOSE) { "Loaded ${named.size} textures containing ${animator.animations.size} animated ones, split into $totalLayers layers!" } Log.log(LogMessageType.RENDERING, LogLevels.VERBOSE) { "Loaded ${named.size} textures containing ${animator.animations.size} animated ones, split into $total layers!" }
animator.init() animator.init()
state = TextureArrayStates.LOADED state = TextureArrayStates.UPLOADED
} }
override fun activate() { override fun activate() {
for ((index, textureId) in textureIds.withIndex()) { for ((index, textureId) in handles.withIndex()) {
if (textureId == -1) { if (textureId == -1) {
continue continue
} }
@ -182,7 +101,7 @@ class OpenGLTextureArray(
shader.use() shader.use()
activate() activate()
for ((index, textureId) in textureIds.withIndex()) { for ((index, textureId) in handles.withIndex()) {
if (textureId == -1) { if (textureId == -1) {
continue continue
} }
@ -192,8 +111,71 @@ class OpenGLTextureArray(
} }
} }
companion object { override fun findResolution(size: Vec2i): Vec2i {
const val TEXTURE_MAX_RESOLUTION = 2048 if (size.x >= MAX_RESOLUTION || size.y >= MAX_RESOLUTION) return Vec2i(MAX_RESOLUTION)
val TEXTURE_RESOLUTION_ID_MAP = intArrayOf(16, 32, 64, 128, 256, 512, 1024, TEXTURE_MAX_RESOLUTION) // A 12x12 texture will be saved in texture id 0 (in 0 are only 16x16 textures). Animated textures get split val array = findArray(size)
if (array < 0) return Vec2i(0, 0)
return Vec2i(RESOLUTIONS[array])
}
private fun findArray(size: Vec2i): Int {
for ((index, resolution) in RESOLUTIONS.withIndex()) {
if (size.x > resolution || size.y > resolution) continue
return index
}
return -1
}
private fun load(animationIndex: AtomicInteger, arrayId: Int, texture: Texture) {
val resolution = RESOLUTIONS[arrayId]
val pixel = PIXEL[arrayId]
val size = texture.size
val uvEnd = if (size.x == resolution && size.y == resolution) null else Vec2(size) / resolution
val array = TextureArrayProperties(uvEnd, resolution, pixel)
if (texture !is SpriteTexture) {
this.resolution[arrayId] += texture
texture.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, -1)
texture.array = array
return
}
val animationIndex = animationIndex.getAndIncrement()
val animation = TextureAnimation(texture)
animator.animations += animation
texture.renderData = OpenGLTextureData(-1, -1, uvEnd, animationIndex)
for (split in texture.splitTextures) {
split.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, animationIndex)
split.array = array
this.resolution[arrayId] += split
}
for (frame in texture.properties.animation!!.frames) {
frame.texture = texture.splitTextures[frame.index]
}
}
override fun load(animationIndex: AtomicInteger, textures: Collection<Texture>) {
for (texture in textures) {
if (texture.size.x > MAX_RESOLUTION || texture.size.y > MAX_RESOLUTION) {
Log.log(LogMessageType.LOADING, LogLevels.WARN) { "Texture $texture exceeds max resolution ($MAX_RESOLUTION): ${texture.size}" }
continue
}
val arrayId = findArray(texture.size)
if (arrayId == -1) {
Log.log(LogMessageType.LOADING, LogLevels.WARN) { "Can not find texture array for $arrayId" }
continue
}
load(animationIndex, arrayId, texture)
}
}
private companion object {
const val MAX_RESOLUTION = 2048
val RESOLUTIONS = intArrayOf(16, 32, 64, 128, 256, 512, 1024, MAX_RESOLUTION) // A 12x12 texture will be saved in texture id 0 (in 0 are only 16x16 textures). Animated textures get split
val PIXEL = FloatArray(RESOLUTIONS.size) { 1.0f / RESOLUTIONS[it] }
} }
} }