Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
// Add dependencies on the required Kotlin modules.
implementation("org.reflections:reflections:0.10.2")
implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion")
implementation("com.pngencoder:pngencoder:0.15.0")

// Add Kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
Expand Down
10 changes: 10 additions & 0 deletions common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ void onTickPost(CallbackInfo ci) {
EventFlow.post(new TickEvent.Post());
}

@Inject(method = "render", at = @At("HEAD"))
void onLoopTickPre(CallbackInfo ci) {
EventFlow.post(new TickEvent.Render.Pre());
}

@Inject(method = "render", at = @At("RETURN"))
void onLoopTickPost(CallbackInfo ci) {
EventFlow.post(new TickEvent.Render.Post());
}

@Inject(at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false), method = "stop")
private void onShutdown(CallbackInfo ci) {
EventFlow.post(new ClientEvent.Shutdown());
Expand Down
9 changes: 1 addition & 8 deletions common/src/main/kotlin/com/lambda/core/Loader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,7 @@ object Loader {
LOG.info("Initializing ${Lambda.MOD_NAME} ${Lambda.VERSION}")

val initTime = measureTimeMillis {
loadables.forEach { loadable ->
val info: String
val phaseTime = measureTimeMillis {
info = loadable.load()
}

LOG.info("$info in ${phaseTime}ms")
}
loadables.forEach { LOG.info(it.load()) }
}

LOG.info("${Lambda.MOD_NAME} ${Lambda.VERSION} was successfully initialized (${initTime}ms)")
Expand Down
93 changes: 74 additions & 19 deletions common/src/main/kotlin/com/lambda/event/events/TickEvent.kt
Original file line number Diff line number Diff line change
@@ -1,43 +1,98 @@
package com.lambda.event.events

import com.lambda.event.Event
import com.lambda.event.EventFlow
import com.lambda.event.events.TickEvent.Post
import com.lambda.event.events.TickEvent.Pre

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

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

/**
* A class representing a [TickEvent] that is triggered when the player gets ticked.
* Triggered before ([Pre]) and after ([Post]) each render tick.
*
* Phases:
*
* 1. **Pre-Render**: Prepares the window for rendering, checks for window close, handles resource reloads.
* 2. **Task Execution**: Executes pending render tasks.
* 3. **Client Tick**: Ticks the client ([TickEvent.Pre] and [TickEvent.Post]) until tick target was met.
* 4. **Render**: Performs the actual rendering of the game.
* 5. **Post-Render**: Finalizes the rendering process, updates the window.
*
* @see net.minecraft.client.MinecraftClient.render
*/
abstract class Render : TickEvent() {
/**
* Triggered before each render tick ([TickEvent.Render]) of the game loop.
*/
class Pre : TickEvent()

/**
* Triggered after each render tick ([TickEvent.Render]) of the game loop.
*/
class Post : TickEvent()
}

/**
* Triggered before ([Pre]) and after ([Post]) each player tick that is run during the game loop [TickEvent.Pre].
*
* Phases:
*
* 1. **Pre-Tick**: Prepares player state before the tick.
* 2. **Movement**: Handles player movement and input.
* 3. **Action**: Processes player actions like swinging hand.
* 4. **Post-Tick**: Finalizes player state after the tick.
*
* @see net.minecraft.client.network.ClientPlayerEntity.tick
*/
abstract class Player : TickEvent() {
/**
* A class representing a [TickEvent.Player] that is triggered before each player tick.
* Triggered before each player tick ([TickEvent.Player]).
*/
class Pre : Player()

/**
* A class representing a [TickEvent.Player] that is triggered after each player tick.
* Triggered after each player tick ([TickEvent.Player]).
*/
class Post : Player()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.lambda.graphics.buffer.vao.vertex
package com.lambda.graphics.buffer

import com.lambda.graphics.gl.GLObject
import org.lwjgl.opengl.GL30C.GL_DYNAMIC_DRAW
import org.lwjgl.opengl.GL30C.GL_STATIC_DRAW
import org.lwjgl.opengl.GL30C.GL_STREAM_DRAW

enum class BufferUsage(override val gl: Int) : GLObject {
STATIC(GL_STATIC_DRAW),
DYNAMIC(GL_DYNAMIC_DRAW)
}
DYNAMIC(GL_DYNAMIC_DRAW),
STREAM(GL_STREAM_DRAW);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.lambda.graphics.buffer

import com.lambda.Lambda.mc
import com.lambda.graphics.RenderMain
import com.lambda.graphics.buffer.vao.VAO
Expand Down Expand Up @@ -124,4 +125,4 @@ class FrameBuffer(private val depth: Boolean = false) {
private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV)
private var lastFrameBuffer: Int? = null
}
}
}
169 changes: 169 additions & 0 deletions common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.lambda.graphics.buffer.pbo

import com.lambda.Lambda.LOG
import com.lambda.graphics.buffer.BufferUsage
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL45C.*
import java.nio.ByteBuffer

/**
* Represents a Pixel Buffer Object (PBO) that facilitates asynchronous data transfer to the GPU.
* This class manages the creation, usage, and cleanup of PBOs and provides methods to map textures and upload data efficiently.
*
* @property width The width of the texture in pixels.
* @property height The height of the texture in pixels.
* @property buffers The number of PBOs to be used. Default is 2, which allows double buffering.
* @property bufferUsage The usage pattern of the buffer, indicating how the buffer will be used (static, dynamic, etc.).
*/
class PixelBuffer(
private val width: Int,
private val height: Int,
private val buffers: Int = 2,
private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC,
) {
private val pboIds = IntArray(buffers)
private var writeIdx = 0 // Used to copy pixels from the PBO to the texture
private var uploadIdx = 0 // Used to upload data to the PBO

private val queryId = glGenQueries() // Used to measure the time taken to upload data to the PBO
private val uploadTime get() =
IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }.first()
private var transferRate = 0L // The transfer rate in bytes per second

private val pboSupported = GL.getCapabilities().OpenGL30 || GL.getCapabilities().GL_ARB_pixel_buffer_object

private var initialDataSent: Boolean = false

/**
* Maps the given texture ID to the buffer and performs the necessary operations to upload the texture data.
*
* @param id The texture ID to which the buffer will be mapped.
* @param buffer The [ByteBuffer] containing the pixel data to be uploaded to the texture.
*/
fun mapTexture(id: Int, buffer: ByteBuffer) {
if (!initialDataSent) {
glBindTexture(GL_TEXTURE_2D, id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
glBindTexture(GL_TEXTURE_2D, 0)

initialDataSent = true

return
}

upload(buffer) {
// Bind the texture
glBindTexture(GL_TEXTURE_2D, id)

if (buffers > 0 && pboSupported) {
// Bind the next PBO to update pixel values
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx])

// Perform the actual data transfer to the GPU
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
} else {
// Perform the actual data transfer to the GPU
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer)
}

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

// Unbind the texture
glBindTexture(GL_TEXTURE_2D, 0)
}
}

/**
* Uploads the given pixel data to the PBO and executes the provided processing function to manage the PBO's data transfer.
*
* @param data The [ByteBuffer] containing the pixel data to be uploaded.
* @param process A lambda function to execute after uploading the data to manage the PBO's data transfer.
*/
fun upload(data: ByteBuffer, process: () -> Unit) =
recordTransfer {
if (buffers >= 2)
uploadIdx = (writeIdx + 1) % buffers

// Copy the pixel values from the PBO to the texture
process()

// Bind the current PBO for writing
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx])

// Note that glMapBuffer() causes sync issue.
// If GPU is working with this buffer, glMapBuffer() will wait(stall)
// until GPU to finish its job. To avoid waiting (idle), you can call
// first glBufferData() with NULL pointer before glMapBuffer().
// If you do that, the previous data in PBO will be discarded and
// glMapBuffer() returns a new allocated pointer immediately
// even if GPU is still working with the previous data.
glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl)

// Map the buffer into the memory
val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)
if (bufferData != null) {
bufferData.put(data)

// Release the buffer
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)
} else throw IllegalStateException("Failed to map the buffer")

// Unbind the PBO
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)

// Swap the indices
writeIdx = uploadIdx
}

/**
* Measures and records the time taken to transfer data to the PBO, calculating the transfer rate in bytes per second.
*
* @param block A lambda function representing the block of code where the transfer occurs.
*/
private fun recordTransfer(block: () -> Unit) {
// Start the timer
glBeginQuery(GL_TIME_ELAPSED, queryId)

// Perform the transfer
block()

// Stop the timer
glEndQuery(GL_TIME_ELAPSED)

// Calculate the transfer rate
val time = uploadTime
if (time > 0) transferRate = (width * height * 4L * 1_000_000_000) / time
}

/**
* Cleans up resources by deleting the PBOs when the object is no longer in use.
*/
fun finalize() {
// Delete the PBOs
glDeleteBuffers(pboIds)
}

/**
* Initializes the PBOs, allocates memory for them, and handles unsupported PBO scenarios.
*
* @throws IllegalArgumentException If the number of buffers is less than 0.
*/
init {
if (buffers < 0) throw IllegalArgumentException("Buffers must be greater than or equal to 0")

if (!pboSupported && buffers > 0)
LOG.warn("Client tried to utilize PBOs, but they are not supported on the machine, falling back to direct buffer upload")

// Generate the PBOs
glGenBuffers(pboIds)

// Fill the buffers with null data to allocate the memory spaces
repeat(buffers) {
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[it])
glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl)
}

glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Unbind the buffer
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.lambda.graphics.buffer.vao

import com.lambda.graphics.buffer.vao.vertex.BufferUsage
import com.lambda.graphics.buffer.BufferUsage
import com.lambda.graphics.buffer.vao.vertex.VertexAttrib
import com.lambda.graphics.buffer.vao.vertex.VertexMode
import com.lambda.graphics.gl.Matrices
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.lambda.event.events.TickEvent
import com.lambda.event.events.WorldEvent
import com.lambda.event.listener.SafeListener.Companion.concurrentListener
import com.lambda.event.listener.SafeListener.Companion.listener
import com.lambda.graphics.buffer.vao.vertex.BufferUsage
import com.lambda.graphics.buffer.BufferUsage
import com.lambda.graphics.renderer.esp.impl.ESPRenderer
import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer
import com.lambda.module.modules.client.RenderSettings
Expand Down Expand Up @@ -126,4 +126,4 @@ class ChunkedESP private constructor(
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.lambda.event.EventFlow.post
import com.lambda.event.events.RenderEvent
import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listener
import com.lambda.graphics.buffer.vao.vertex.BufferUsage
import com.lambda.graphics.buffer.BufferUsage
import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer

object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) {
Expand All @@ -15,4 +15,4 @@ object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) {
upload()
}
}
}
}
Loading