Skip to content

Commit f15eff5

Browse files
committed
better texture handlers and fixed slots
1 parent ca5066f commit f15eff5

File tree

9 files changed

+137
-158
lines changed

9 files changed

+137
-158
lines changed

common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.lambda.graphics.buffer.vertex.attributes.VertexMode
2323
import com.lambda.graphics.renderer.gui.font.LambdaAtlas.bind
2424
import com.lambda.graphics.renderer.gui.font.LambdaAtlas.get
2525
import com.lambda.graphics.renderer.gui.font.LambdaAtlas.height
26+
import com.lambda.graphics.renderer.gui.font.LambdaAtlas.slot
2627
import com.lambda.graphics.shader.Shader
2728
import com.lambda.module.modules.client.ClickGui
2829
import com.lambda.module.modules.client.LambdaMoji
@@ -221,7 +222,8 @@ class FontRenderer {
221222

222223
fun render() {
223224
shader.use()
224-
shader["u_EmojiTexture"] = 1
225+
shader["u_FontTexture"] = chars.slot
226+
shader["u_EmojiTexture"] = emojis.slot
225227

226228
chars.bind()
227229
emojis.bind()

common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaAtlas.kt

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
package com.lambda.graphics.renderer.gui.font
1919

2020
import com.google.common.math.IntMath.pow
21-
import com.lambda.event.events.ConnectionEvent
22-
import com.lambda.event.listener.UnsafeListener.Companion.unsafeListenOnce
23-
import com.lambda.graphics.texture.MipmapTexture
21+
import com.lambda.core.Loadable
22+
import com.lambda.graphics.texture.TextureHandler.texture
23+
import com.lambda.graphics.texture.TextureHandler.upload
2424
import com.lambda.http.Method
2525
import com.lambda.http.request
26-
import com.lambda.module.modules.client.RenderSettings
2726
import com.lambda.threading.runGameScheduled
2827
import com.lambda.util.LambdaResource
2928
import com.lambda.util.math.Vec2d
@@ -60,7 +59,8 @@ import kotlin.time.Duration.Companion.days
6059
*
6160
* fun loadFont(...) = BufferedImage
6261
*
63-
* ExampleFont.CoolFont.uploadAtlas(loadFont(...)) // The extension keeps a reference to the font owner
62+
* // Function extension from [TexturePipeline]
63+
* ExampleFont.CoolFont.upload(loadFont(...)) // The extension keeps a reference to the font owner
6464
*
6565
* ...
6666
*
@@ -69,11 +69,10 @@ import kotlin.time.Duration.Companion.days
6969
* }
7070
* ```
7171
*/
72-
object LambdaAtlas {
72+
object LambdaAtlas : Loadable {
7373
private val fontMap = Object2ObjectOpenHashMap<Any, Int2ObjectArrayMap<GlyphInfo>>()
7474
private val emojiMap = Object2ObjectOpenHashMap<Any, Object2ObjectOpenHashMap<String, GlyphInfo>>()
75-
private val textureMap = Object2ObjectOpenHashMap<Any, MipmapTexture>()
76-
private val slotReservation = Object2IntArrayMap<Any>()
75+
private val slotReservation = Object2IntArrayMap<Any>() // Will cause undefined behavior if someone is trying to allocate more than 32 slots, unlikely
7776

7877
private val bufferPool =
7978
mutableMapOf<Any, BufferedImage>() // This array is nuked once the data is dispatched to OpenGL
@@ -85,20 +84,15 @@ object LambdaAtlas {
8584
operator fun LambdaFont.get(char: Char): GlyphInfo? = fontMap.getValue(this)[char.code]
8685
operator fun LambdaEmoji.get(string: String): GlyphInfo? = emojiMap.getValue(this)[string]
8786

88-
/**
89-
* Upload additional atlas that can be used with the owner to bind textures to shaders
90-
*/
91-
fun Any.uploadAtlas(data: BufferedImage) = textureMap.set(this, MipmapTexture(data))
92-
9387
// Allow binding any valid font definition enums
94-
fun <T : Enum<T>> T.bind() = with(textureMap.getValue(this))
95-
{
96-
bind(slot = slotReservation.computeIfAbsent(this, ToIntFunction { slotReservation.size }))
97-
setLOD(RenderSettings.lodBias.toFloat())
98-
}
88+
fun <T : Enum<T>> T.bind() =
89+
this@bind.texture.bind(slot = slotReservation.computeIfAbsent(this@bind, ToIntFunction { slotReservation.size }))
90+
91+
val <T : Enum<T>> T.slot: Int
92+
get() = slotReservation.getInt(this@slot)
9993

10094
val LambdaFont.height: Double
101-
get() = heightCache.getDouble(fontCache[this])
95+
get() = heightCache.getDouble(fontCache[this@height])
10296

10397
val LambdaEmoji.keys
10498
get() = emojiMap.getValue(this)
@@ -215,19 +209,16 @@ object LambdaAtlas {
215209
bufferPool[this] = image
216210
}
217211

218-
init {
219-
// TODO: Change this when we've refactored the loadables
220-
unsafeListenOnce<ConnectionEvent.Connect.Pre, ConnectionEvent.Connect.Pre> {
221-
runGameScheduled {
222-
bufferPool.forEach { (owner, image) ->
223-
textureMap[owner] = MipmapTexture(image)
224-
}
225-
226-
bufferPool.clear()
227-
}
212+
// TODO: Change this when we've refactored the loadables
213+
override fun load(): String {
214+
val str = "Loaded ${bufferPool.size} fonts" // avoid race condition
228215

229-
true
216+
runGameScheduled {
217+
bufferPool.forEach { (owner, image) -> owner.upload(image) }
218+
bufferPool.clear()
230219
}
220+
221+
return str
231222
}
232223

233224
private fun getCharImage(font: Font, codePoint: Char): BufferedImage? {

common/src/main/kotlin/com/lambda/graphics/texture/MipmapTexture.kt

Lines changed: 0 additions & 65 deletions
This file was deleted.

common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,58 @@
1818
package com.lambda.graphics.texture
1919

2020
import com.lambda.graphics.texture.TextureUtils.bindTexture
21+
import com.lambda.graphics.texture.TextureUtils.readImage
22+
import com.lambda.graphics.texture.TextureUtils.setupTexture
23+
import com.lambda.module.modules.client.RenderSettings
2124
import org.lwjgl.opengl.GL45C.*
25+
import java.awt.image.BufferedImage
2226

23-
open class Texture {
27+
open class Texture(
28+
private val image: BufferedImage?,
29+
private val levels: Int = 4,
30+
) {
2431
val id = glGenTextures()
2532

26-
fun bind(slot: Int = 0) = bindTexture(id, slot)
33+
open fun init() = image
34+
?.let {
35+
bind()
36+
upload(it)
37+
bind(0)
38+
}
39+
40+
open fun bind(slot: Int = 0) {
41+
bindTexture(id, slot)
42+
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, RenderSettings.lodBias)
43+
}
44+
45+
open fun upload(image: BufferedImage, offset: Int = 0) {
46+
// Store level_base +1 through `level` images and generate
47+
// mipmaps from them
48+
setupLOD(levels = levels)
49+
50+
val width = image.width
51+
val height = image.height
52+
53+
// Set this mipmap to 0 to define the original texture
54+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, readImage(image))
55+
glGenerateMipmap(GL_TEXTURE_2D) // This take the derived values GL_TEXTURE_BASE_LEVEL and GL_TEXTURE_MAX_LEVEL to generate the stack
56+
57+
setupTexture(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR)
58+
}
59+
60+
private fun setupLOD(levels: Int) {
61+
// When you call glTextureStorage, you're specifying the total number of levels, including level 0
62+
// This is a 0-based index system, which means that the maximum mipmap level is n-1
63+
//
64+
// TLDR: This will not work correctly with immutable texture storage
65+
66+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0)
67+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, levels)
68+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0)
69+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels)
70+
}
71+
72+
init {
73+
init() // The overridden method will run and not the base one due to kotlin's order of execution ;)
74+
}
2775
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2024 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.graphics.texture
19+
20+
import com.lambda.util.LambdaResource
21+
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
22+
import java.awt.image.BufferedImage
23+
24+
object TextureHandler {
25+
private val textureMap = Object2ObjectOpenHashMap<Any, Texture>()
26+
27+
/**
28+
* Returns the texture owned by a specific object
29+
*/
30+
val Any.texture: Texture
31+
get() = textureMap.getValue(this@texture)
32+
33+
/**
34+
* Generate mipmap texture from data and associate it with its owner
35+
*/
36+
fun Any.upload(data: BufferedImage, mipmaps: Int = 1) =
37+
Texture(data, levels = mipmaps).also { textureMap[this@upload] = it }
38+
39+
/**
40+
* Generate mipmap texture from data and associate it with its owner
41+
*
42+
* @param path Lambda resource path containing the image data
43+
*/
44+
fun Any.upload(path: String, mipmaps: Int = 1) =
45+
Texture(LambdaResource.readImage(path), levels = mipmaps).also { textureMap[this@upload] = it }
46+
}

common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ import com.pngencoder.PngEncoder
2222
import net.minecraft.client.texture.NativeImage
2323
import org.lwjgl.BufferUtils
2424
import org.lwjgl.opengl.GL45C.*
25-
import java.awt.*
2625
import java.awt.image.BufferedImage
2726
import java.nio.ByteBuffer
28-
import kotlin.math.roundToInt
29-
import kotlin.math.sqrt
3027

3128
object TextureUtils {
3229
private const val COMPRESSION_LEVEL = 1
@@ -41,22 +38,6 @@ object TextureUtils {
4138
RenderSystem.bindTexture(id)
4239
}
4340

44-
fun upload(bufferedImage: BufferedImage, lod: Int) {
45-
val width = bufferedImage.width
46-
val height = bufferedImage.height
47-
48-
glTexImage2D(GL_TEXTURE_2D, lod, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, readImage(bufferedImage))
49-
50-
setupTexture(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR)
51-
}
52-
53-
fun setupLOD(levels: Int) {
54-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0)
55-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, levels)
56-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0)
57-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels)
58-
}
59-
6041
fun setupTexture(minFilter: Int, magFilter: Int) {
6142
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter)
6243
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter)
@@ -89,39 +70,4 @@ object TextureUtils {
8970
image: ByteBuffer,
9071
format: NativeImage.Format = NativeImage.Format.RGBA,
9172
) = NativeImage.read(format, image).pointer
92-
93-
fun BufferedImage.rescale(targetWidth: Int, targetHeight: Int): BufferedImage {
94-
val type = if (transparency == Transparency.OPAQUE)
95-
BufferedImage.TYPE_INT_RGB
96-
else BufferedImage.TYPE_INT_ARGB
97-
98-
var image = this
99-
100-
var width = image.width
101-
var height = image.height
102-
103-
val divisorX = sqrt((width / targetWidth).toDouble())
104-
val divisorY = sqrt((height / targetHeight).toDouble())
105-
106-
do {
107-
if (width > targetWidth) {
108-
width = (width / divisorX).roundToInt().coerceAtLeast(targetWidth)
109-
}
110-
111-
if (height > targetHeight) {
112-
height = (height / divisorY).roundToInt().coerceAtLeast(targetHeight)
113-
}
114-
115-
val tempImage = BufferedImage(width, height, type)
116-
val graphics2D = tempImage.createGraphics()
117-
118-
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
119-
graphics2D.drawImage(image, 0, 0, width, height, null)
120-
graphics2D.dispose()
121-
122-
image = tempImage
123-
} while (width != targetWidth || height != targetHeight)
124-
125-
return image
126-
}
12773
}

common/src/main/kotlin/com/lambda/module/hud/Watermark.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.lambda.module.hud
1919

2020
import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture
2121
import com.lambda.graphics.renderer.gui.TextureRenderer.drawTextureShaded
22-
import com.lambda.graphics.texture.MipmapTexture
22+
import com.lambda.graphics.texture.TextureHandler.upload
2323
import com.lambda.module.HudModule
2424
import com.lambda.module.tag.ModuleTag
2525

@@ -32,8 +32,8 @@ object Watermark : HudModule(
3232
override val width = 50.0
3333
override val height = 50.0
3434

35-
private val normalTexture = MipmapTexture.fromResource("textures/lambda.png")
36-
private val monoTexture = MipmapTexture.fromResource("textures/lambda_mono.png")
35+
private val normalTexture = upload("textures/lambda.png")
36+
private val monoTexture = upload("textures/lambda_mono.png")
3737

3838
init {
3939
onRender {

0 commit comments

Comments
 (0)