Skip to content

Commit 3cd4cc8

Browse files
emyfopsbladektAvanatiker
authored
[1.20.x] [All] Feat: Various GL optimization (#63)
# Description This pull request utilize few techniques to speed up the initial loading process by: - Adding a cache or compute system that can be used anywhere for I/O - Includes a much better PNG decoder for writing data The game now launches 2x to 3x faster Additionally, with the new reflections utilities, all reflections logging can be displayed at the top of the console, preventing it from flooding our initial logging. However, this comes with the trade-off of excluding measureTimeMillisecond in each phase, except the final one (Personally, I prefer it this way :trollface:) This pull request also add Blade's game loop event Please test this pr before merging --------- Co-authored-by: Blade-gl <bladecore.kt@gmail.com> Co-authored-by: Constructor <fractalminds@protonmail.com>
1 parent 4cacf41 commit 3cd4cc8

File tree

31 files changed

+476
-151
lines changed

31 files changed

+476
-151
lines changed

common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dependencies {
2323
// Add dependencies on the required Kotlin modules.
2424
implementation("org.reflections:reflections:0.10.2")
2525
implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion")
26+
implementation("com.pngencoder:pngencoder:0.15.0")
2627

2728
// Add Kotlin
2829
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")

common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ void onTickPost(CallbackInfo ci) {
3535
EventFlow.post(new TickEvent.Post());
3636
}
3737

38+
@Inject(method = "render", at = @At("HEAD"))
39+
void onLoopTickPre(CallbackInfo ci) {
40+
EventFlow.post(new TickEvent.Render.Pre());
41+
}
42+
43+
@Inject(method = "render", at = @At("RETURN"))
44+
void onLoopTickPost(CallbackInfo ci) {
45+
EventFlow.post(new TickEvent.Render.Post());
46+
}
47+
3848
@Inject(at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false), method = "stop")
3949
private void onShutdown(CallbackInfo ci) {
4050
EventFlow.post(new ClientEvent.Shutdown());

common/src/main/kotlin/com/lambda/core/Loader.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,7 @@ object Loader {
4545
LOG.info("Initializing ${Lambda.MOD_NAME} ${Lambda.VERSION}")
4646

4747
val initTime = measureTimeMillis {
48-
loadables.forEach { loadable ->
49-
val info: String
50-
val phaseTime = measureTimeMillis {
51-
info = loadable.load()
52-
}
53-
54-
LOG.info("$info in ${phaseTime}ms")
55-
}
48+
loadables.forEach { LOG.info(it.load()) }
5649
}
5750

5851
LOG.info("${Lambda.MOD_NAME} ${Lambda.VERSION} was successfully initialized (${initTime}ms)")

common/src/main/kotlin/com/lambda/event/events/TickEvent.kt

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,98 @@
11
package com.lambda.event.events
22

33
import com.lambda.event.Event
4-
import com.lambda.event.EventFlow
5-
import com.lambda.event.events.TickEvent.Post
6-
import com.lambda.event.events.TickEvent.Pre
74

8-
/**
9-
* An abstract class representing a [TickEvent] in the [EventFlow].
10-
*
11-
* A [TickEvent] is a type of [Event] that is triggered at each tick of the game loop.
12-
* It has two subclasses: [Pre] and [Post], which are triggered before and after the tick, respectively.
13-
*
14-
* The [TickEvent] class is designed to be extended by any class that needs to react to ticks.
15-
*
16-
* @see Pre
17-
* @see Post
18-
*/
195
abstract class TickEvent : Event {
206
/**
21-
* A class representing a [TickEvent] that is triggered before each tick of the game loop.
7+
* Triggered before each iteration of the game loop.
8+
*
9+
* Phases:
10+
*
11+
* 1. **Pre-Tick**: Increments uptime, steps world tick manager, decrement item use cooldown.
12+
* 2. **GUI Update**: Processes delayed messages, updates HUD.
13+
* 3. **Game Mode Update**: Updates targeted entity, ticks tutorial, and interaction managers.
14+
* 4. **Texture Update**: Ticks texture manager.
15+
* 5. **Screen Handling**: Manages screen logic, ticks current screen.
16+
* 6. **Debug HUD Update**: Resets debug HUD chunk.
17+
* 7. **Input Handling**: Handles input events, decrements attack cooldown.
18+
* 8. **World Update**: Ticks game and world renderers, world entities.
19+
* 9. **Music and Sound Update**: Ticks music tracker and sound manager.
20+
* 10. **Tutorial and Social Interactions**: Handles tutorial and social interactions, ticks world.
21+
* 11. **Pending Connection**: Ticks integrated server connection.
22+
* 12. **Keyboard Handling**: Polls for debug crash key presses.
23+
*
24+
* @see net.minecraft.client.MinecraftClient.tick
2225
*/
2326
class Pre : TickEvent()
2427

2528
/**
26-
* A class representing a [TickEvent] that is triggered after each tick of the game loop.
29+
* Triggered after each iteration of the game loop.
30+
* Targeted at 20 ticks per second.
31+
*
32+
* Phases:
33+
*
34+
* 1. **Pre-Tick**: Increments uptime, steps world tick manager, decrement item use cooldown.
35+
* 2. **GUI Update**: Processes delayed messages, updates HUD.
36+
* 3. **Game Mode Update**: Updates targeted entity, ticks tutorial, and interaction managers.
37+
* 4. **Texture Update**: Ticks texture manager.
38+
* 5. **Screen Handling**: Manages screen logic, ticks current screen.
39+
* 6. **Debug HUD Update**: Resets debug HUD chunk.
40+
* 7. **Input Handling**: Handles input events, decrements attack cooldown.
41+
* 8. **World Update**: Ticks game and world renderers, world entities (such as [TickEvent.Player]).
42+
* 9. **Music and Sound Update**: Ticks music tracker and sound manager.
43+
* 10. **Tutorial and Social Interactions**: Handles tutorial and social interactions, ticks world.
44+
* 11. **Pending Connection**: Ticks integrated server connection.
45+
* 12. **Keyboard Handling**: Polls for debug crash key presses.
46+
*
47+
* @see net.minecraft.client.MinecraftClient.tick
2748
*/
2849
class Post : TickEvent()
2950

3051
/**
31-
* A class representing a [TickEvent] that is triggered when the player gets ticked.
52+
* Triggered before ([Pre]) and after ([Post]) each render tick.
53+
*
54+
* Phases:
55+
*
56+
* 1. **Pre-Render**: Prepares the window for rendering, checks for window close, handles resource reloads.
57+
* 2. **Task Execution**: Executes pending render tasks.
58+
* 3. **Client Tick**: Ticks the client ([TickEvent.Pre] and [TickEvent.Post]) until tick target was met.
59+
* 4. **Render**: Performs the actual rendering of the game.
60+
* 5. **Post-Render**: Finalizes the rendering process, updates the window.
61+
*
62+
* @see net.minecraft.client.MinecraftClient.render
63+
*/
64+
abstract class Render : TickEvent() {
65+
/**
66+
* Triggered before each render tick ([TickEvent.Render]) of the game loop.
67+
*/
68+
class Pre : TickEvent()
69+
70+
/**
71+
* Triggered after each render tick ([TickEvent.Render]) of the game loop.
72+
*/
73+
class Post : TickEvent()
74+
}
75+
76+
/**
77+
* Triggered before ([Pre]) and after ([Post]) each player tick that is run during the game loop [TickEvent.Pre].
78+
*
79+
* Phases:
80+
*
81+
* 1. **Pre-Tick**: Prepares player state before the tick.
82+
* 2. **Movement**: Handles player movement and input.
83+
* 3. **Action**: Processes player actions like swinging hand.
84+
* 4. **Post-Tick**: Finalizes player state after the tick.
85+
*
86+
* @see net.minecraft.client.network.ClientPlayerEntity.tick
3287
*/
3388
abstract class Player : TickEvent() {
3489
/**
35-
* A class representing a [TickEvent.Player] that is triggered before each player tick.
90+
* Triggered before each player tick ([TickEvent.Player]).
3691
*/
3792
class Pre : Player()
3893

3994
/**
40-
* A class representing a [TickEvent.Player] that is triggered after each player tick.
95+
* Triggered after each player tick ([TickEvent.Player]).
4196
*/
4297
class Post : Player()
4398
}

common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt renamed to common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
package com.lambda.graphics.buffer.vao.vertex
1+
package com.lambda.graphics.buffer
22

33
import com.lambda.graphics.gl.GLObject
44
import org.lwjgl.opengl.GL30C.GL_DYNAMIC_DRAW
55
import org.lwjgl.opengl.GL30C.GL_STATIC_DRAW
6+
import org.lwjgl.opengl.GL30C.GL_STREAM_DRAW
67

78
enum class BufferUsage(override val gl: Int) : GLObject {
89
STATIC(GL_STATIC_DRAW),
9-
DYNAMIC(GL_DYNAMIC_DRAW)
10-
}
10+
DYNAMIC(GL_DYNAMIC_DRAW),
11+
STREAM(GL_STREAM_DRAW);
12+
}

common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package com.lambda.graphics.buffer
2+
23
import com.lambda.Lambda.mc
34
import com.lambda.graphics.RenderMain
45
import com.lambda.graphics.buffer.vao.VAO
@@ -124,4 +125,4 @@ class FrameBuffer(private val depth: Boolean = false) {
124125
private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV)
125126
private var lastFrameBuffer: Int? = null
126127
}
127-
}
128+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package com.lambda.graphics.buffer.pbo
2+
3+
import com.lambda.Lambda.LOG
4+
import com.lambda.graphics.buffer.BufferUsage
5+
import org.lwjgl.opengl.GL
6+
import org.lwjgl.opengl.GL45C.*
7+
import java.nio.ByteBuffer
8+
9+
/**
10+
* Represents a Pixel Buffer Object (PBO) that facilitates asynchronous data transfer to the GPU.
11+
* This class manages the creation, usage, and cleanup of PBOs and provides methods to map textures and upload data efficiently.
12+
*
13+
* @property width The width of the texture in pixels.
14+
* @property height The height of the texture in pixels.
15+
* @property buffers The number of PBOs to be used. Default is 2, which allows double buffering.
16+
* @property bufferUsage The usage pattern of the buffer, indicating how the buffer will be used (static, dynamic, etc.).
17+
*/
18+
class PixelBuffer(
19+
private val width: Int,
20+
private val height: Int,
21+
private val buffers: Int = 2,
22+
private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC,
23+
) {
24+
private val pboIds = IntArray(buffers)
25+
private var writeIdx = 0 // Used to copy pixels from the PBO to the texture
26+
private var uploadIdx = 0 // Used to upload data to the PBO
27+
28+
private val queryId = glGenQueries() // Used to measure the time taken to upload data to the PBO
29+
private val uploadTime get() =
30+
IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }.first()
31+
private var transferRate = 0L // The transfer rate in bytes per second
32+
33+
private val pboSupported = GL.getCapabilities().OpenGL30 || GL.getCapabilities().GL_ARB_pixel_buffer_object
34+
35+
private var initialDataSent: Boolean = false
36+
37+
/**
38+
* Maps the given texture ID to the buffer and performs the necessary operations to upload the texture data.
39+
*
40+
* @param id The texture ID to which the buffer will be mapped.
41+
* @param buffer The [ByteBuffer] containing the pixel data to be uploaded to the texture.
42+
*/
43+
fun mapTexture(id: Int, buffer: ByteBuffer) {
44+
if (!initialDataSent) {
45+
glBindTexture(GL_TEXTURE_2D, id)
46+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
47+
glBindTexture(GL_TEXTURE_2D, 0)
48+
49+
initialDataSent = true
50+
51+
return
52+
}
53+
54+
upload(buffer) {
55+
// Bind the texture
56+
glBindTexture(GL_TEXTURE_2D, id)
57+
58+
if (buffers > 0 && pboSupported) {
59+
// Bind the next PBO to update pixel values
60+
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx])
61+
62+
// Perform the actual data transfer to the GPU
63+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
64+
} else {
65+
// Perform the actual data transfer to the GPU
66+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer)
67+
}
68+
69+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
70+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
71+
72+
// Unbind the texture
73+
glBindTexture(GL_TEXTURE_2D, 0)
74+
}
75+
}
76+
77+
/**
78+
* Uploads the given pixel data to the PBO and executes the provided processing function to manage the PBO's data transfer.
79+
*
80+
* @param data The [ByteBuffer] containing the pixel data to be uploaded.
81+
* @param process A lambda function to execute after uploading the data to manage the PBO's data transfer.
82+
*/
83+
fun upload(data: ByteBuffer, process: () -> Unit) =
84+
recordTransfer {
85+
if (buffers >= 2)
86+
uploadIdx = (writeIdx + 1) % buffers
87+
88+
// Copy the pixel values from the PBO to the texture
89+
process()
90+
91+
// Bind the current PBO for writing
92+
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx])
93+
94+
// Note that glMapBuffer() causes sync issue.
95+
// If GPU is working with this buffer, glMapBuffer() will wait(stall)
96+
// until GPU to finish its job. To avoid waiting (idle), you can call
97+
// first glBufferData() with NULL pointer before glMapBuffer().
98+
// If you do that, the previous data in PBO will be discarded and
99+
// glMapBuffer() returns a new allocated pointer immediately
100+
// even if GPU is still working with the previous data.
101+
glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl)
102+
103+
// Map the buffer into the memory
104+
val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)
105+
if (bufferData != null) {
106+
bufferData.put(data)
107+
108+
// Release the buffer
109+
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)
110+
} else throw IllegalStateException("Failed to map the buffer")
111+
112+
// Unbind the PBO
113+
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)
114+
115+
// Swap the indices
116+
writeIdx = uploadIdx
117+
}
118+
119+
/**
120+
* Measures and records the time taken to transfer data to the PBO, calculating the transfer rate in bytes per second.
121+
*
122+
* @param block A lambda function representing the block of code where the transfer occurs.
123+
*/
124+
private fun recordTransfer(block: () -> Unit) {
125+
// Start the timer
126+
glBeginQuery(GL_TIME_ELAPSED, queryId)
127+
128+
// Perform the transfer
129+
block()
130+
131+
// Stop the timer
132+
glEndQuery(GL_TIME_ELAPSED)
133+
134+
// Calculate the transfer rate
135+
val time = uploadTime
136+
if (time > 0) transferRate = (width * height * 4L * 1_000_000_000) / time
137+
}
138+
139+
/**
140+
* Cleans up resources by deleting the PBOs when the object is no longer in use.
141+
*/
142+
fun finalize() {
143+
// Delete the PBOs
144+
glDeleteBuffers(pboIds)
145+
}
146+
147+
/**
148+
* Initializes the PBOs, allocates memory for them, and handles unsupported PBO scenarios.
149+
*
150+
* @throws IllegalArgumentException If the number of buffers is less than 0.
151+
*/
152+
init {
153+
if (buffers < 0) throw IllegalArgumentException("Buffers must be greater than or equal to 0")
154+
155+
if (!pboSupported && buffers > 0)
156+
LOG.warn("Client tried to utilize PBOs, but they are not supported on the machine, falling back to direct buffer upload")
157+
158+
// Generate the PBOs
159+
glGenBuffers(pboIds)
160+
161+
// Fill the buffers with null data to allocate the memory spaces
162+
repeat(buffers) {
163+
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[it])
164+
glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl)
165+
}
166+
167+
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Unbind the buffer
168+
}
169+
}

common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.lambda.graphics.buffer.vao
22

3-
import com.lambda.graphics.buffer.vao.vertex.BufferUsage
3+
import com.lambda.graphics.buffer.BufferUsage
44
import com.lambda.graphics.buffer.vao.vertex.VertexAttrib
55
import com.lambda.graphics.buffer.vao.vertex.VertexMode
66
import com.lambda.graphics.gl.Matrices

common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.lambda.event.events.TickEvent
55
import com.lambda.event.events.WorldEvent
66
import com.lambda.event.listener.SafeListener.Companion.concurrentListener
77
import com.lambda.event.listener.SafeListener.Companion.listener
8-
import com.lambda.graphics.buffer.vao.vertex.BufferUsage
8+
import com.lambda.graphics.buffer.BufferUsage
99
import com.lambda.graphics.renderer.esp.impl.ESPRenderer
1010
import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer
1111
import com.lambda.module.modules.client.RenderSettings
@@ -126,4 +126,4 @@ class ChunkedESP private constructor(
126126
}
127127
}
128128
}
129-
}
129+
}

common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.lambda.event.EventFlow.post
44
import com.lambda.event.events.RenderEvent
55
import com.lambda.event.events.TickEvent
66
import com.lambda.event.listener.SafeListener.Companion.listener
7-
import com.lambda.graphics.buffer.vao.vertex.BufferUsage
7+
import com.lambda.graphics.buffer.BufferUsage
88
import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer
99

1010
object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) {
@@ -15,4 +15,4 @@ object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) {
1515
upload()
1616
}
1717
}
18-
}
18+
}

0 commit comments

Comments
 (0)