Skip to content

Commit 31b24f1

Browse files
committed
feat: gif renderer
1 parent e645ef2 commit 31b24f1

File tree

7 files changed

+156
-58
lines changed

7 files changed

+156
-58
lines changed

common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
package com.lambda.graphics.buffer.pixel
1919

2020
import com.lambda.graphics.buffer.IBuffer
21-
import com.lambda.graphics.gl.padding
22-
import com.lambda.graphics.gl.putTo
2321
import com.lambda.graphics.texture.Texture
2422
import org.lwjgl.opengl.GL45C.*
2523
import java.nio.ByteBuffer
@@ -46,82 +44,52 @@ class PixelBuffer(
4644
private val texture: Texture,
4745
private val format: Int,
4846
) : IBuffer {
49-
override val buffers: Int = 2
47+
override val buffers: Int = 1
5048
override val usage: Int = GL_STATIC_DRAW
5149
override val target: Int = GL_PIXEL_UNPACK_BUFFER
52-
override val access: Int = GL_MAP_WRITE_BIT or GL_MAP_COHERENT_BIT
50+
override val access: Int = GL_MAP_WRITE_BIT
5351
override var index = 0
5452
override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) }
5553

56-
private val channels = channelMapping[format] ?: throw IllegalArgumentException("Image format unsupported")
57-
private val internalFormat = reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported")
54+
private val channels = channelMapping[format] ?: throw IllegalArgumentException("Invalid image format, expected OpenGL format, got $format instead")
55+
private val internalFormat = reverseChannelMapping[channels] ?: throw IllegalArgumentException("Invalid internal image format, expected channels count, got $channels instead")
5856
private val size = width * height * channels * 1L
5957

6058
override fun upload(
6159
data: ByteBuffer,
6260
offset: Long,
6361
): Throwable? {
64-
// Bind PBO to unpack the data into the texture
6562
bind()
66-
67-
// Bind the texture and PBO
6863
glBindTexture(GL_TEXTURE_2D, texture.id)
6964

7065
// Copy pixels from PBO to texture object
7166
// Use offset instead of pointer
7267
glTexSubImage2D(
7368
GL_TEXTURE_2D, // Target
74-
0, // Mipmap level
75-
0, 0, // x and y offset
69+
0, // Mipmap level
70+
0, 0, // x and y offset
7671
width, height, // width and height of the texture (set to your size)
7772
format, // Format (depends on your data)
7873
GL_UNSIGNED_BYTE, // Type (depends on your data)
79-
0, // PBO offset (for asynchronous transfer)
74+
0, // PBO offset (for asynchronous transfer)
8075
)
8176

82-
// Unbind the texture
83-
glBindTexture(GL_TEXTURE_2D, 0)
84-
85-
// Swap the buffer
86-
swap()
87-
88-
// Bind PBO to update pixel source
89-
bind()
90-
91-
// Map the buffer into the client's memory
92-
val error = map(offset, size, data::putTo)
77+
val error = update(data, offset)
9378

94-
// Unbind
9579
bind(0)
9680

9781
return error
9882
}
9983

10084
init {
101-
// Bind the texture
10285
glBindTexture(GL_TEXTURE_2D, texture.id)
10386

104-
// Calculate memory padding in the case we are using tightly
105-
// packed data in order to save memory and satisfy the computer's
106-
// architecture memory alignment
107-
// https://en.wikipedia.org/wiki/Data_structure_alignment
108-
// In this case we calculate the padding and subtract this to 4
109-
// in order to tell the padding size
110-
glPixelStorei(GL_UNPACK_ALIGNMENT, 4 - padding(channels))
111-
11287
// Allocate texture storage
113-
// TODO: Might want to figure out the data type based on the input
11488
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, 0)
115-
116-
// Set the texture parameters
11789
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
11890
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
11991

120-
// Unbind the texture
121-
glBindTexture(GL_TEXTURE_2D, 0)
122-
123-
// Fill the storage with null
124-
storage(size)
92+
allocate(size)
12593
}
12694

12795
companion object {

common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,4 @@ val Long.kibibyte get() = this * 1024
113113
val Long.mebibyte get() = this * 1024 * 1024
114114
val Long.gibibyte get() = this * 1024 * 1024 * 1024
115115

116-
/**
117-
* Returns memory alignment for each CPU architecture
118-
*/
119-
fun alignment(): Int {
120-
return when (System.getProperty("os.arch")?.lowercase()) {
121-
"x86", "x86_64" -> 4 // 32-bit or 64-bit x86
122-
"arm", "armv7l", "aarch64" -> 4 // ARM architectures
123-
else -> 8 // Default to 8 bytes alignment for other architectures
124-
}
125-
}
126-
127-
/**
128-
* Returns how many bytes will be added to reach memory alignment
129-
*/
130-
fun padding(size: Int): Int = size % alignment() / 8
131-
132116
fun ByteBuffer.putTo(dst: ByteBuffer) { dst.put(this) }
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.graphics.buffer.pixel.PixelBuffer
21+
import com.lambda.util.Communication.logError
22+
import com.lambda.util.LambdaResource
23+
import com.lambda.util.stream
24+
import org.lwjgl.BufferUtils
25+
import org.lwjgl.opengl.GL11.GL_RGBA
26+
import org.lwjgl.stb.STBImage
27+
import java.nio.ByteBuffer
28+
import kotlin.properties.Delegates
29+
30+
31+
class AnimatedTexture(
32+
private val path: LambdaResource,
33+
) : Texture(null) {
34+
lateinit var frameDurations: IntArray
35+
var width by Delegates.notNull<Int>()
36+
var height by Delegates.notNull<Int>()
37+
var channels by Delegates.notNull<Int>()
38+
var frames by Delegates.notNull<Int>()
39+
40+
val blockSize: Int
41+
get() = width * height * channels
42+
43+
lateinit var gif: ByteBuffer // Do NOT free this pointer
44+
var pbo: PixelBuffer
45+
46+
var currentFrame = 0
47+
var lastUpload = 0L
48+
49+
override fun bind(slot: Int) {
50+
update()
51+
super.bind(slot)
52+
}
53+
54+
fun update() {
55+
if (System.currentTimeMillis() - lastUpload >= frameDurations[currentFrame]) {
56+
val slice = gif
57+
.position(blockSize * currentFrame)
58+
.limit(blockSize * (currentFrame + 1))
59+
60+
pbo.upload(slice, offset = 0)
61+
?.let { err -> logError("Error uploading to PBO", err) }
62+
63+
gif.clear()
64+
65+
currentFrame = (currentFrame+1) % frames
66+
lastUpload = System.currentTimeMillis()
67+
}
68+
}
69+
70+
private fun readGif() {
71+
val bytes = path.stream.readAllBytes()
72+
val buffer = ByteBuffer.allocateDirect(bytes.size)
73+
74+
buffer.put(bytes)
75+
buffer.flip()
76+
77+
val pDelays = BufferUtils.createPointerBuffer(1)
78+
val pWidth = BufferUtils.createIntBuffer(1)
79+
val pHeight = BufferUtils.createIntBuffer(1)
80+
val pLayers = BufferUtils.createIntBuffer(1)
81+
val pChannels = BufferUtils.createIntBuffer(1)
82+
83+
// The buffer contains packed frames that can be extracted as follows:
84+
// limit = width * height * channels * [frame number]
85+
gif = STBImage.stbi_load_gif_from_memory(buffer, pDelays, pWidth, pHeight, pLayers, pChannels, 4)!!
86+
87+
width = pWidth.get()
88+
height = pHeight.get()
89+
frames = pLayers.get()
90+
channels = pChannels.get()
91+
92+
frameDurations = IntArray(frames)
93+
pDelays.getIntBuffer(frames).get(frameDurations)
94+
}
95+
96+
init {
97+
readGif()
98+
pbo = PixelBuffer(width, height, this@AnimatedTexture, format = GL_RGBA)
99+
}
100+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package com.lambda.graphics.texture
1919

20-
import com.lambda.util.LambdaResource
2120
import com.lambda.util.readImage
2221
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
2322
import java.awt.image.BufferedImage
@@ -44,4 +43,10 @@ object TextureOwner {
4443
*/
4544
fun Any.upload(path: String, mipmaps: Int = 1) =
4645
Texture(path.readImage(), levels = mipmaps).also { textureMap[this@upload] = it }
46+
47+
/**
48+
* Loads a gif and associate it with its owner
49+
*/
50+
fun Any.uploadGif(path: String) =
51+
AnimatedTexture(path).also { textureMap[this@uploadGif] = it }
4752
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.module.hud
19+
20+
import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture
21+
import com.lambda.graphics.texture.TextureOwner.uploadGif
22+
import com.lambda.module.HudModule
23+
import com.lambda.module.tag.ModuleTag
24+
25+
object GifTest : HudModule(
26+
name = "GifTest",
27+
defaultTags = setOf(ModuleTag.CLIENT),
28+
) {
29+
val test = uploadGif("chika.gif")
30+
31+
override val width = 100.0
32+
override val height = 100.0
33+
34+
init {
35+
onRender {
36+
drawTexture(test, rect)
37+
}
38+
}
39+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ package com.lambda.module.hud
2020
import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture
2121
import com.lambda.graphics.renderer.gui.TextureRenderer.drawTextureShaded
2222
import com.lambda.graphics.texture.TextureOwner.upload
23+
import com.lambda.graphics.texture.TextureOwner.uploadGif
2324
import com.lambda.module.HudModule
2425
import com.lambda.module.tag.ModuleTag
26+
import com.lambda.util.math.Vec2d
2527

2628
object Watermark : HudModule(
2729
name = "Watermark",
442 KB
Loading

0 commit comments

Comments
 (0)