From f3757f6fbd378ef88e70e925cac6a3c84f298d8e Mon Sep 17 00:00:00 2001 From: Blade-gl Date: Thu, 8 Aug 2024 16:02:05 +0300 Subject: [PATCH 01/45] Global toggle sounds --- .../lambda/gui/impl/clickgui/buttons/ModuleButton.kt | 12 ++++-------- common/src/main/kotlin/com/lambda/module/Module.kt | 10 ++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/ModuleButton.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/ModuleButton.kt index 86ec9933c..df80cf068 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/ModuleButton.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/ModuleButton.kt @@ -174,12 +174,11 @@ class ModuleButton( } override fun performClickAction(e: GuiEvent.MouseClick) { - val sound = when (e.button) { + when (e.button) { Mouse.Button.Left -> { module.toggle() - if (module.isEnabled) LambdaSound.MODULE_ON else LambdaSound.MODULE_OFF - } + } Mouse.Button.Right -> { // Don't let user spam val targetHeight = if (isOpen) settingsHeight else 0.0 @@ -189,13 +188,10 @@ class ModuleButton( if (isOpen) settingsLayer.onEvent(GuiEvent.Show()) updateHeight() - if (isOpen) LambdaSound.SETTINGS_OPEN else LambdaSound.SETTINGS_CLOSE + val sound = if (isOpen) LambdaSound.SETTINGS_OPEN else LambdaSound.SETTINGS_CLOSE + playSoundRandomly(sound.event) } - - else -> return } - - playSoundRandomly(sound.event) } override fun equals(other: Any?) = diff --git a/common/src/main/kotlin/com/lambda/module/Module.kt b/common/src/main/kotlin/com/lambda/module/Module.kt index 4cca48a40..61545b980 100644 --- a/common/src/main/kotlin/com/lambda/module/Module.kt +++ b/common/src/main/kotlin/com/lambda/module/Module.kt @@ -18,6 +18,8 @@ import com.lambda.gui.impl.clickgui.LambdaClickGui import com.lambda.gui.impl.clickgui.buttons.ModuleButton import com.lambda.module.modules.client.ClickGui import com.lambda.module.tag.ModuleTag +import com.lambda.sound.LambdaSound +import com.lambda.sound.SoundManager.playSoundRandomly import com.lambda.util.KeyCode import com.lambda.util.Nameable @@ -119,6 +121,14 @@ abstract class Module( || screen is LambdaClickGui) ) toggle() } + + onEnable { + playSoundRandomly(LambdaSound.MODULE_ON.event) + } + + onDisable { + playSoundRandomly(LambdaSound.MODULE_OFF.event) + } } fun enable() { From 65751e3e0d84ae6c7d2c43bb16107c9383187f98 Mon Sep 17 00:00:00 2001 From: Blade-gl Date: Sat, 10 Aug 2024 13:37:37 +0300 Subject: [PATCH 02/45] Tickshift improvements --- .../lambda/mixin/MinecraftClientMixin.java | 10 ++++++++++ .../com/lambda/event/events/TickEvent.kt | 19 +++++++++++++++++-- .../lambda/module/modules/movement/Blink.kt | 18 ++++++++++++------ .../module/modules/movement/TickShift.kt | 11 +++++++---- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 9ffcd5c65..6c92d6558 100644 --- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -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.GameLoop.Pre()); + } + + @Inject(method = "render", at = @At("RETURN")) + void onLoopTickPost(CallbackInfo ci) { + EventFlow.post(new TickEvent.GameLoop.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()); diff --git a/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt b/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt index 85f9b1a0a..d47597fdb 100644 --- a/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt @@ -18,15 +18,30 @@ import com.lambda.event.events.TickEvent.Pre */ abstract class TickEvent : Event { /** - * A class representing a [TickEvent] that is triggered before each tick of the game loop. + * A class representing a [TickEvent] that is triggered before each tick of the tick loop. */ class Pre : TickEvent() /** - * A class representing a [TickEvent] that is triggered after each tick of the game loop. + * A class representing a [TickEvent] that is triggered after each tick of the tick loop. */ class Post : TickEvent() + /** + * A class representing a [TickEvent] that is triggered on each tick of the game loop. + */ + abstract class GameLoop : TickEvent() { + /** + * A class representing a [TickEvent.Player] that is triggered before each tick of the game loop. + */ + class Pre : TickEvent() + + /** + * A class representing a [TickEvent.Player] that is triggered after each tick of the game loop. + */ + class Post : TickEvent() + } + /** * A class representing a [TickEvent] that is triggered when the player gets ticked. */ diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt index ddae9757b..702931923 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt @@ -14,10 +14,12 @@ import com.lambda.util.ClientPacket import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently import com.lambda.util.math.ColorUtils.setAlpha +import com.lambda.util.math.VecUtils.minus import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box +import net.minecraft.util.math.Vec3d import java.util.concurrent.ConcurrentLinkedDeque object Blink : Module( @@ -61,6 +63,16 @@ object Blink : Module( return@listener } + listener { event -> + val packet = event.packet + if (packet !is PlayerMoveC2SPacket) return@listener + + val vec = Vec3d(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0)) + if (vec == Vec3d.ZERO) return@listener + + lastBox = player.boundingBox.offset(vec - player.pos) + } + listener { event -> if (!isActive || !shiftVelocity) return@listener @@ -81,12 +93,6 @@ object Blink : Module( while (packetPool.isNotEmpty()) { packetPool.poll().let { packet -> connection.sendPacketSilently(packet) - - if (packet is PlayerMoveC2SPacket && packet.changesPosition()) { - lastBox = player.boundingBox - .offset(player.pos.negate()) - .offset(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0)) - } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt index 1f9d92f1c..893674986 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt @@ -26,10 +26,13 @@ object TickShift : Module( private val boostAmount by setting("Boost", 3.0, 1.1..20.0, 0.01) private val slowdown by setting("Slowdown", 0.35, 0.01..0.9, 0.01) private val delaySetting by setting("Delay", 0, 0..2000, 10) - private val strict by setting("Strict", true) - private val shiftVelocity by setting("Shift velocity", true) + private val grim by setting("Grim", true) + private val strictSetting by setting("Strict", true) { !grim } + private val shiftVelocity by setting("Shift velocity", true) { grim } private val requiresAura by setting("Requires Aura", false) + private val strict get() = grim || strictSetting + val isActive: Boolean get() { if (requiresAura && (!KillAura.isEnabled || KillAura.target == null)) return false return System.currentTimeMillis() - lastBoost > delaySetting @@ -90,7 +93,7 @@ object TickShift : Module( } listener { event -> - if (!isActive) return@listener + if (!isActive || !grim || event.isCanceled()) return@listener if (event.packet !is CommonPongC2SPacket) return@listener pingPool.add(event.packet) @@ -99,7 +102,7 @@ object TickShift : Module( } listener { event -> - if (!isActive || !shiftVelocity) return@listener + if (!isActive || !grim || !shiftVelocity || event.isCanceled()) return@listener if (event.packet !is EntityVelocityUpdateS2CPacket) return@listener if (event.packet.id != player.id) return@listener From 81a0c6324759ea41d7bb7bebbb313eae668cd26e Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:30:30 -0400 Subject: [PATCH 03/45] various performance tweaks (3x faster load time) --- common/build.gradle.kts | 1 + .../graphics/renderer/gui/font/LambdaEmoji.kt | 2 +- .../renderer/gui/font/glyph/EmojiGlyphs.kt | 31 ++++---- .../renderer/gui/font/glyph/FontGlyphs.kt | 66 +++++++++------- .../lambda/graphics/texture/MipmapTexture.kt | 8 +- .../lambda/graphics/texture/TextureUtils.kt | 30 +++----- .../main/kotlin/com/lambda/http/Request.kt | 23 ++++-- .../module/modules/client/RenderSettings.kt | 7 +- .../kotlin/com/lambda/util/FolderRegister.kt | 75 ++++++++++++++++++- fabric/build.gradle.kts | 1 + forge/build.gradle.kts | 10 +-- neoforge/build.gradle.kts | 1 + 12 files changed, 170 insertions(+), 85 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index c918e1820..07481fb66 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -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") diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt index 87cef41fd..8d8f85f48 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt @@ -17,7 +17,7 @@ enum class LambdaEmoji(private val zipUrl: String) { object Loader : Loadable { override fun load(): String { entries.forEach(LambdaEmoji::loadGlyphs) - return "Loaded ${entries.size} emoji pools" + return "Loaded ${entries.size} emoji sets" } } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt index d8f92e6e6..0405a0dcf 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt @@ -3,23 +3,22 @@ package com.lambda.graphics.renderer.gui.font.glyph import com.google.common.math.IntMath.pow import com.lambda.Lambda.LOG import com.lambda.graphics.texture.MipmapTexture +import com.lambda.http.Method +import com.lambda.http.request import com.lambda.module.modules.client.RenderSettings -import com.lambda.threading.runGameScheduled -import com.lambda.threading.runIO import com.lambda.util.math.Vec2d import java.awt.Color import java.awt.Graphics2D import java.awt.image.BufferedImage import java.io.File -import java.net.URL import java.util.zip.ZipFile import javax.imageio.ImageIO import kotlin.math.ceil import kotlin.math.log2 import kotlin.math.sqrt import kotlin.system.measureTimeMillis +import kotlin.time.Duration.Companion.days -// TODO: AbstractGlyphs to use for both Font & Emoji glyphs? class EmojiGlyphs(zipUrl: String) { private val emojiMap = mutableMapOf() private lateinit var fontTexture: MipmapTexture @@ -29,9 +28,7 @@ class EmojiGlyphs(zipUrl: String) { init { runCatching { - val time = measureTimeMillis { - downloadAndProcessZip(zipUrl) - } + val time = measureTimeMillis { downloadAndProcessZip(zipUrl) } LOG.info("Loaded ${emojiMap.size} emojis in $time ms") }.onFailure { LOG.error("Failed to load emojis: ${it.message}", it) @@ -40,14 +37,20 @@ class EmojiGlyphs(zipUrl: String) { } private fun downloadAndProcessZip(zipUrl: String) { - val file = File.createTempFile("emoji", ".zip").apply { deleteOnExit() } + val file = request(zipUrl) { + method(Method.GET) + }.maybeDownload("emojis.zip", maxAge = 30.days) - URL(zipUrl).openStream().use { input -> - file.outputStream().use { output -> - input.copyTo(output) - } - } + fontTexture = MipmapTexture(processZip(file)) + } + /** + * Processes the given zip file and loads the emojis into the texture. + * + * @param file The zip file containing the emojis. + * @return The texture containing the emojis. + */ + private fun processZip(file: File): BufferedImage { ZipFile(file).use { zip -> val firstImage = ImageIO.read(zip.getInputStream(zip.entries().nextElement())) val length = zip.size().toDouble() @@ -90,7 +93,7 @@ class EmojiGlyphs(zipUrl: String) { } } - fontTexture = MipmapTexture(image) + return image } fun bind() { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt index e00cbb284..3c492b26b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt @@ -1,6 +1,6 @@ package com.lambda.graphics.renderer.gui.font.glyph -import com.lambda.Lambda +import com.lambda.Lambda.LOG import com.lambda.graphics.texture.MipmapTexture import com.lambda.graphics.texture.TextureUtils.getCharImage import com.lambda.module.modules.client.RenderSettings @@ -13,52 +13,60 @@ import java.awt.image.BufferedImage import kotlin.math.max import kotlin.system.measureTimeMillis -class FontGlyphs(font: Font) { +class FontGlyphs( + private val font: Font +) { private val charMap = Int2ObjectOpenHashMap() - private val fontTexture: MipmapTexture + private lateinit var fontTexture: MipmapTexture var fontHeight = 0.0; private set init { - val time = measureTimeMillis { - val image = BufferedImage(TEXTURE_SIZE, TEXTURE_SIZE, BufferedImage.TYPE_INT_ARGB) + runCatching { + val time = measureTimeMillis { processGlyphs() } + LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters in $time ms") + }.onFailure { + LOG.error("Failed to load font glyphs: ${it.message}", it) + fontTexture = MipmapTexture(BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB)) + } + } - val graphics = image.graphics as Graphics2D - graphics.background = Color(0, 0, 0, 0) + private fun processGlyphs() { + val image = BufferedImage(TEXTURE_SIZE, TEXTURE_SIZE, BufferedImage.TYPE_INT_ARGB) - var x = 0 - var y = 0 - var rowHeight = 0 + val graphics = image.graphics as Graphics2D + graphics.background = Color(0, 0, 0, 0) - (Char.MIN_VALUE.. - val charImage = getCharImage(font, char) ?: return@forEach + var x = 0 + var y = 0 + var rowHeight = 0 - rowHeight = max(rowHeight, charImage.height + STEP) + (Char.MIN_VALUE.. + val charImage = getCharImage(font, char) ?: return@forEach - if (x + charImage.width >= TEXTURE_SIZE) { - y += rowHeight - x = 0 - rowHeight = 0 - } + rowHeight = max(rowHeight, charImage.height + STEP) - check(y + charImage.height <= TEXTURE_SIZE) { "Can't load font glyphs. Texture size is too small" } + if (x + charImage.width >= TEXTURE_SIZE) { + y += rowHeight + x = 0 + rowHeight = 0 + } - graphics.drawImage(charImage, x, y, null) + check(y + charImage.height <= TEXTURE_SIZE) { "Can't load font glyphs. Texture size is too small" } - val size = Vec2d(charImage.width, charImage.height) - val uv1 = Vec2d(x, y) * ONE_TEXEL_SIZE - val uv2 = Vec2d(x, y).plus(size) * ONE_TEXEL_SIZE + graphics.drawImage(charImage, x, y, null) - charMap[char.code] = GlyphInfo(size, uv1, uv2) - fontHeight = max(fontHeight, size.y) + val size = Vec2d(charImage.width, charImage.height) + val uv1 = Vec2d(x, y) * ONE_TEXEL_SIZE + val uv2 = Vec2d(x, y).plus(size) * ONE_TEXEL_SIZE - x += charImage.width + STEP - } + charMap[char.code] = GlyphInfo(size, uv1, uv2) + fontHeight = max(fontHeight, size.y) - fontTexture = MipmapTexture(image) + x += charImage.width + STEP } - Lambda.LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters in $time ms") + fontTexture = MipmapTexture(image) } fun bind() { diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/MipmapTexture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/MipmapTexture.kt index f52906d64..ea918e92a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/MipmapTexture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/MipmapTexture.kt @@ -36,7 +36,13 @@ class MipmapTexture(image: BufferedImage, levels: Int = 4) : Texture() { } companion object { + /** + * Retrieves an image from the resources folder and generates a mipmap texture. + * + * @param path The path to the image. + * @param levels The number of mipmap levels. + */ fun fromResource(path: String, levels: Int = 4): MipmapTexture = MipmapTexture(ImageIO.read(LambdaResource(path).stream), levels) } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt index 5bb814180..5ed6289cb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt @@ -1,19 +1,22 @@ package com.lambda.graphics.texture +import com.lambda.module.modules.client.RenderSettings import com.mojang.blaze3d.systems.RenderSystem +import com.pngencoder.PngEncoder import net.minecraft.client.texture.NativeImage import org.lwjgl.BufferUtils -import org.lwjgl.opengl.GL13C.* +import org.lwjgl.opengl.GL45C.* import java.awt.* import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream -import javax.imageio.ImageIO import kotlin.math.roundToInt import kotlin.math.sqrt object TextureUtils { private val metricCache = mutableMapOf() - + private val encoderPreset = PngEncoder() + .withCompressionLevel(RenderSettings.textureCompression) + .withMultiThreadedCompressionEnabled(RenderSettings.threadedCompression) fun bindTexture(id: Int, slot: Int = 0) { RenderSystem.activeTexture(GL_TEXTURE0 + slot) @@ -35,21 +38,6 @@ object TextureUtils { // Array of floats normalized to [0.0, 1.0] -> [R, G, B, A] glTexImage2D(GL_TEXTURE_2D, lod, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, readImage(bufferedImage)) - // I'd also like to use glTexSubImage2D, but we have an issue where the function - // would return an error about an invalid texture format. - // - // It would allow us to upload texture data asynchronously and is more efficient - // from testing we gain approximately 20% runtime performance. - // If someone with advanced OpenGL knowledge could help us out, that would be great. - // (Very unlikely to happen, but I can hope) - // - // I've also read online that glTexStorage2D can be used for the same purpose as - // glTexImage2D with NULL data. - // However, some users may have ancient hardware that does not support this function. - // as it was implemented in OpenGL 4.2 and ES 3.0. - // - // glTexSubImage2D(GL_TEXTURE_2D, lod, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, readImage(bufferedImage)) - setupTexture(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR) } @@ -73,10 +61,10 @@ object TextureUtils { } private fun readImage(bufferedImage: BufferedImage): Long { - val stream = ByteArrayOutputStream() - ImageIO.write(bufferedImage, "png", stream) + val bytes = encoderPreset + .withBufferedImage(bufferedImage) + .toBytes() - val bytes = stream.toByteArray() val buffer = BufferUtils .createByteBuffer(bytes.size) .put(bytes) diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index f975fa0ff..c3002601c 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -2,10 +2,12 @@ package com.lambda.http import com.lambda.Lambda import com.lambda.util.FolderRegister.cache +import com.lambda.util.FolderRegister.createFileIfNotExists +import com.lambda.util.FolderRegister.createIfNotExists import java.io.File +import java.io.OutputStream import java.net.HttpURLConnection import java.net.URL -import java.time.Instant import kotlin.time.Duration import kotlin.time.Duration.Companion.days @@ -33,14 +35,21 @@ data class Request( /** * Downloads the resource at the specified path and caches it for future use. * - * @param path The path to the resource. + * @param name The full name of the file to be cached. * @param maxAge The maximum age of the cached resource. Default is 4 days. + * + * @return A pair containing the cached file and a boolean indicating whether the file was downloaded. */ - fun maybeDownload(path: String, maxAge: Duration = 4.days): ByteArray { - val file = File("${cache}/${path.substringAfterLast("/").hashCode()}") + fun maybeDownload( + name: String, + maxAge: Duration = 7.days, + ): File { + val (file, wasCreated) = createFileIfNotExists(name, cache, true) - if (file.exists() && Instant.now().toEpochMilli() - file.lastModified() < maxAge.inWholeMilliseconds) - return file.readBytes() + if (System.currentTimeMillis() - file.lastModified() < maxAge.inWholeMilliseconds + && file.length() > 0 + && !wasCreated) + return file file.writeText("") // Clear the file before writing to it. @@ -64,7 +73,7 @@ data class Request( } } - return file.readBytes() + return file } /** diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt index c2726bd6f..ec29fad21 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt @@ -18,6 +18,10 @@ object RenderSettings : Module( val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font } private val lodBiasSetting by setting("Smoothing", 0.0, -10.0..10.0, 0.5) { page == Page.Font } + // Texture + val textureCompression by setting("Compression", 1, 1..9, 1, description = "Texture compression level, higher is slower") { page == Page.TEXTURE } + val threadedCompression by setting("Threaded Compression", false, description = "Use multiple threads for texture compression") { page == Page.TEXTURE } + // ESP val uploadsPerTick by setting("Uploads", 16, 1..256, 1, unit = " chunk/tick") { page == Page.ESP } val rebuildsPerTick by setting("Rebuilds", 64, 1..256, 1, unit = " chunk/tick") { page == Page.ESP } @@ -28,6 +32,7 @@ object RenderSettings : Module( private enum class Page { Font, - ESP + TEXTURE, + ESP, } } diff --git a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt index 26af87db9..86e698d57 100644 --- a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt +++ b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt @@ -8,8 +8,11 @@ import com.lambda.util.FolderRegister.mods import com.lambda.util.FolderRegister.packetLogs import com.lambda.util.FolderRegister.replay import com.lambda.util.StringUtils.sanitizeForFilename +import org.apache.commons.codec.digest.DigestUtils import java.io.File +import java.io.InputStream import java.net.InetSocketAddress +import java.security.MessageDigest /** * The [FolderRegister] object is responsible for managing the directory structure of the application. @@ -31,9 +34,7 @@ object FolderRegister { val cache: File = File(lambda, "cache") fun File.createIfNotExists() { - if (!exists()) { - mkdirs() - } + createFileIfNotExists(this.name, this.parentFile) } fun File.listRecursive(predicate: (File) -> Boolean = { true }) = walk().filter(predicate) @@ -48,4 +49,72 @@ object FolderRegister { path.createIfNotExists() return path } + + /** + * Returns a file with the given name in the specified directory, creating it if it does not exist. + * If the directory is not specified, it will try to parse it from the name. + * Otherwise, it will default to the Lambda directory. + * + * @param name The name of the file. + * @param directory The directory in which the file is located. Default is the Lambda directory. + * @param hash Whether to hash the name of the file. + * @return A pair containing the file and a boolean indicating whether the file was created. + */ + fun createFileIfNotExists(name: String, directory: File? = null, hash: Boolean = false): Pair { + var parsedDir: File = directory ?: lambda + + if (directory == null) { + parsedDir = name.substringAfterLast('/').substringBeforeLast('.') + .let { if (it.isEmpty()) lambda else File(it) } + } + + val compiledName = + if (hash) DigestUtils.sha256Hex(name) + else name + + val file = File(parsedDir, compiledName) + val created = !file.exists() + + if (created) { + file.parentFile.mkdirs() + file.createNewFile() + } + + return file to created + } + + + /** + * Returns a file with the given name in the specified directory, creating it if it does not exist. + * If the directory is not specified, it will try to parse it from the name. + * Otherwise, it will default to the Lambda directory. + * + * @param name The name of the file. + * @param directory The directory in which the file is located. Default is the Lambda directory. + * @param compute A lambda function to compute the file contents if it was created. + */ + @JvmName("getFileOrComputeByteArray") + inline fun getFileOrCompute(name: String, directory: File? = null, compute: () -> ByteArray): File { + val (file, wasCreated) = createFileIfNotExists(name, directory) + if (wasCreated) file.outputStream().use { it.write(compute()) } + + return file + } + + /** + * Returns a file with the given name in the specified directory, creating it if it does not exist. + * If the directory is not specified, it will try to parse it from the name. + * Otherwise, it will default to the Lambda directory. + * + * @param name The name of the file. + * @param directory The directory in which the file is located. Default is the Lambda directory. + * @param compute A lambda function to compute the file contents if it was created. + */ + @JvmName("getFileOrComputeInputStream") + inline fun getFileOrCompute(name: String, directory: File? = null, compute: () -> InputStream): File { + val (file, wasCreated) = createFileIfNotExists(name, directory) + if (wasCreated) file.outputStream().use { compute().copyTo(it) } + + return file + } } diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 2cbb32749..8844cc10f 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { includeLib("org.javassist:javassist:3.28.0-GA") includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") + includeLib("com.pngencoder:pngencoder:0.15.0") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 479861fa3..935fa55ed 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -80,14 +80,8 @@ dependencies { // Add dependencies on the required Kotlin modules. includeLib("org.reflections:reflections:0.10.2") includeLib("org.javassist:javassist:3.28.0-GA") - - // Temporary, only works for production - // See https://github.com/MinecraftForge/MinecraftForge/issues/8878 - includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") { - exclude(group = "org.jetbrains.kotlin") - exclude(group = "org.jetbrains.kotlinx") - exclude(group = "org.slf4j") - } + includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") + includeLib("com.pngencoder:pngencoder:0.15.0") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 12c4af40c..9978da288 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { includeLib("org.javassist:javassist:3.28.0-GA") includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") + includeLib("com.pngencoder:pngencoder:0.15.0") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge-neoforge:$kotlinForgeVersion") From ea972be77204e4f0e97fb2de1f95589b18ee54b3 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:16:07 -0400 Subject: [PATCH 04/45] todo: pixel buffer object --- .../com/lambda/graphics/buffer/PixelBuffer.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt new file mode 100644 index 000000000..26e0e9828 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt @@ -0,0 +1,66 @@ +package com.lambda.graphics.buffer + +import org.lwjgl.opengl.GL45C.* +import java.nio.ByteBuffer + +// NOT TESTED +class PixelBuffer( + width: Int, + height: Int +) { + private val pboIds = IntArray(2) { 0 } + private var index = 0 + + fun upload(data: ByteBuffer, block: () -> Unit) { + // Bind the current PBO for writing + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) + + // Map the buffer and copy data into it + val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) as ByteBuffer + bufferData.put(data) + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) + + // Process the data + block() + + // Unbind the buffer + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + + // Switch to the other PBO + index = (index + 1) % 2 + } + + fun download(): ByteBuffer { + // Bind the current PBO for reading + glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[index]) + + // Map the buffer and copy data from it + val bufferData = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY) as ByteBuffer + val data = bufferData.slice() + + // Unbind the buffer + glUnmapBuffer(GL_PIXEL_PACK_BUFFER) + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) + + return data + } + + fun finalize() { + // Delete the PBOs + glDeleteBuffers(pboIds) + } + + init { + // Generate the PBOs + glGenBuffers(pboIds) + + // Fill the buffers with null data to allocate the memory spaces + glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[0]) + glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4L, GL_DYNAMIC_READ) + glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[1]) + glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4L, GL_DYNAMIC_READ) + + // Unbind the buffer + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) + } +} From c6328ca34cd436ab7c246578d39b9c575da54c66 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:25:45 -0400 Subject: [PATCH 05/45] pixel n-buffer object --- .../com/lambda/graphics/buffer/PixelBuffer.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt index 26e0e9828..97a53fbaf 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt @@ -6,9 +6,10 @@ import java.nio.ByteBuffer // NOT TESTED class PixelBuffer( width: Int, - height: Int + height: Int, + buffers: Int = 2 ) { - private val pboIds = IntArray(2) { 0 } + private val pboIds = IntArray(buffers) { 0 } private var index = 0 fun upload(data: ByteBuffer, block: () -> Unit) { @@ -27,7 +28,7 @@ class PixelBuffer( glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Switch to the other PBO - index = (index + 1) % 2 + index = (index + 1) % pboIds.size } fun download(): ByteBuffer { @@ -55,10 +56,10 @@ class PixelBuffer( glGenBuffers(pboIds) // Fill the buffers with null data to allocate the memory spaces - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[0]) - glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4L, GL_DYNAMIC_READ) - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[1]) - glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4L, GL_DYNAMIC_READ) + repeat(buffers) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[it]) + glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4L, GL_DYNAMIC_READ) + } // Unbind the buffer glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) From 7f35a62d8a5d92b16c92f5d721bc714b46014393 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:41:08 -0400 Subject: [PATCH 06/45] handle empty pbos --- .../com/lambda/graphics/buffer/PixelBuffer.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt index 97a53fbaf..44b84a277 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt @@ -1,17 +1,31 @@ package com.lambda.graphics.buffer +import net.minecraft.client.texture.NativeImage import org.lwjgl.opengl.GL45C.* import java.nio.ByteBuffer -// NOT TESTED class PixelBuffer( - width: Int, - height: Int, - buffers: Int = 2 + private val width: Int, + private val height: Int, + private val buffers: Int = 2 ) { private val pboIds = IntArray(buffers) { 0 } private var index = 0 + fun mapTexture(id: Int, buffer: ByteBuffer) { + upload(buffer) { + // Bind the texture + glBindTexture(GL_TEXTURE_2D, id) + + if (buffers > 0) + // Copy the data from the PBO to the texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + else + // Copy the data from the buffer to the texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NativeImage.read(buffer).pointer) + } + } + fun upload(data: ByteBuffer, block: () -> Unit) { // Bind the current PBO for writing glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) @@ -28,7 +42,7 @@ class PixelBuffer( glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Switch to the other PBO - index = (index + 1) % pboIds.size + if (buffers > 0) index = (index + 1) % buffers } fun download(): ByteBuffer { From 1bba654d9f7f58f959a05d4114d29d43ea93324d Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:42:29 -0400 Subject: [PATCH 07/45] test: streaming to pbo --- common/build.gradle.kts | 1 + .../kotlin/com/lambda/graphics/RenderMain.kt | 2 +- .../buffer/{vao/vertex => }/BufferUsage.kt | 8 +- .../com/lambda/graphics/buffer/PixelBuffer.kt | 81 ---------------- .../graphics/buffer/{ => fbo}/FrameBuffer.kt | 4 +- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 97 +++++++++++++++++++ .../com/lambda/graphics/buffer/vao/VAO.kt | 2 +- .../graphics/renderer/esp/ChunkedESP.kt | 4 +- .../graphics/renderer/esp/global/StaticESP.kt | 4 +- .../renderer/esp/impl/DynamicESPRenderer.kt | 4 +- .../graphics/renderer/esp/impl/ESPRenderer.kt | 2 +- .../renderer/esp/impl/StaticESPRenderer.kt | 4 +- .../com/lambda/graphics/texture/Texture.kt | 6 +- .../com/lambda/graphics/video/JCodecUtils.kt | 74 ++++++++++++++ .../kotlin/com/lambda/graphics/video/Video.kt | 44 +++++++++ .../com/lambda/gui/impl/AbstractClickGui.kt | 8 +- .../kotlin/com/lambda/module/hud/VideoTest.kt | 22 +++++ fabric/build.gradle.kts | 28 ++++++ forge/build.gradle.kts | 1 + neoforge/build.gradle.kts | 1 + 20 files changed, 290 insertions(+), 107 deletions(-) rename common/src/main/kotlin/com/lambda/graphics/buffer/{vao/vertex => }/BufferUsage.kt (60%) delete mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt rename common/src/main/kotlin/com/lambda/graphics/buffer/{ => fbo}/FrameBuffer.kt (99%) create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/Video.kt create mode 100644 common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 07481fb66..eb6433c76 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation("org.reflections:reflections:0.10.2") implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") implementation("com.pngencoder:pngencoder:0.15.0") + implementation("org.jcodec:jcodec:0.2.3") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") diff --git a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt index 998a21872..6eabe8442 100644 --- a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -7,7 +7,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.graphics.animation.AnimationTicker -import com.lambda.graphics.buffer.FrameBuffer +import com.lambda.graphics.buffer.fbo.FrameBuffer import com.lambda.graphics.gl.GlStateUtils.setupGL import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.resetMatrices diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt similarity index 60% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt index 1cd56273d..7300d8e3f 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt @@ -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) -} \ No newline at end of file + DYNAMIC(GL_DYNAMIC_DRAW), + STREAM(GL_STREAM_DRAW); +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt deleted file mode 100644 index 44b84a277..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/PixelBuffer.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.lambda.graphics.buffer - -import net.minecraft.client.texture.NativeImage -import org.lwjgl.opengl.GL45C.* -import java.nio.ByteBuffer - -class PixelBuffer( - private val width: Int, - private val height: Int, - private val buffers: Int = 2 -) { - private val pboIds = IntArray(buffers) { 0 } - private var index = 0 - - fun mapTexture(id: Int, buffer: ByteBuffer) { - upload(buffer) { - // Bind the texture - glBindTexture(GL_TEXTURE_2D, id) - - if (buffers > 0) - // Copy the data from the PBO to the texture - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) - else - // Copy the data from the buffer to the texture - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NativeImage.read(buffer).pointer) - } - } - - fun upload(data: ByteBuffer, block: () -> Unit) { - // Bind the current PBO for writing - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) - - // Map the buffer and copy data into it - val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) as ByteBuffer - bufferData.put(data) - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) - - // Process the data - block() - - // Unbind the buffer - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) - - // Switch to the other PBO - if (buffers > 0) index = (index + 1) % buffers - } - - fun download(): ByteBuffer { - // Bind the current PBO for reading - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[index]) - - // Map the buffer and copy data from it - val bufferData = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY) as ByteBuffer - val data = bufferData.slice() - - // Unbind the buffer - glUnmapBuffer(GL_PIXEL_PACK_BUFFER) - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) - - return data - } - - fun finalize() { - // Delete the PBOs - glDeleteBuffers(pboIds) - } - - init { - // Generate the PBOs - glGenBuffers(pboIds) - - // Fill the buffers with null data to allocate the memory spaces - repeat(buffers) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[it]) - glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4L, GL_DYNAMIC_READ) - } - - // Unbind the buffer - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/fbo/FrameBuffer.kt similarity index 99% rename from common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/fbo/FrameBuffer.kt index e16e32891..25b3bd3bc 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/fbo/FrameBuffer.kt @@ -1,4 +1,4 @@ -package com.lambda.graphics.buffer +package com.lambda.graphics.buffer.fbo import com.lambda.Lambda.mc import com.lambda.graphics.RenderMain import com.lambda.graphics.buffer.vao.VAO @@ -124,4 +124,4 @@ class FrameBuffer(private val depth: Boolean = false) { private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private var lastFrameBuffer: Int? = null } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt new file mode 100644 index 000000000..0c5971dbe --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -0,0 +1,97 @@ +package com.lambda.graphics.buffer.pbo + +import com.lambda.graphics.buffer.BufferUsage +import com.lambda.graphics.texture.TextureUtils.setupTexture +import org.lwjgl.opengl.GL45C.* +import java.nio.ByteBuffer +import kotlin.system.measureNanoTime + +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 + + fun mapTexture(id: Int, buffer: ByteBuffer) { + val time = measureNanoTime { + upload(buffer) { allocate -> + // Bind the texture + glBindTexture(GL_TEXTURE_2D, id) + + if (allocate) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + + // Allocate the texture memory + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) + } + else { + // Update the texture + glTextureSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + } + } + } + + println("PBO => texture $id took $time nanoseconds") + } + + fun upload(data: ByteBuffer, process: (Boolean) -> Unit) { + uploadIdx = (writeIdx + 1) % buffers + + // Bind the next PBO to update pixel values + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) + + // Allocate the memory for the buffer + process(true) + + // 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 { + println("Failed to map the PBO") + } + + // Bind the current PBO for writing + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) + + // Copy the pixel values from the PBO to the texture + process(false) + + // Unbind the PBO + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + + println("Uploaded data to PBO $uploadIdx at $bufferData") + + // Swap the indices + writeIdx = uploadIdx + } + + // Called when no references to the object exist + fun finalize() { + // Delete the PBOs + glDeleteBuffers(pboIds) + } + + init { + if (buffers < 1) throw IllegalArgumentException("Buffers must be greater than or equal to 1") + + // 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, GL_STREAM_DRAW) + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Unbind the buffer + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt index b6cb584cc..ccec90409 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt @@ -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 diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index 966fe139d..1d1f8ef2c 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -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 @@ -126,4 +126,4 @@ class ChunkedESP private constructor( } } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt index 454d624b9..fa40ed0ca 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt @@ -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) { @@ -15,4 +15,4 @@ object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) { upload() } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt index bf8f9a878..e9383f035 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt @@ -1,5 +1,5 @@ package com.lambda.graphics.renderer.esp.impl -import com.lambda.graphics.buffer.vao.vertex.BufferUsage +import com.lambda.graphics.buffer.BufferUsage -open class DynamicESPRenderer : ESPRenderer(BufferUsage.DYNAMIC, true) \ No newline at end of file +open class DynamicESPRenderer : ESPRenderer(BufferUsage.DYNAMIC, true) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt index c35a4a2e5..15d0977da 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt @@ -2,7 +2,7 @@ package com.lambda.graphics.renderer.esp.impl import com.lambda.Lambda.mc import com.lambda.graphics.buffer.vao.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.GlStateUtils.withFaceCulling diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt index 26c7c9140..592412dde 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt @@ -1,7 +1,7 @@ package com.lambda.graphics.renderer.esp.impl import com.lambda.graphics.buffer.vao.IRenderContext -import com.lambda.graphics.buffer.vao.vertex.BufferUsage +import com.lambda.graphics.buffer.BufferUsage import java.awt.Color import java.util.concurrent.ConcurrentHashMap @@ -45,4 +45,4 @@ open class StaticESPRenderer( } data class Vertex(val x: Double, val y: Double, val z: Double, val color: Color) -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index 7512b523b..ff5990c87 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -1,12 +1,10 @@ package com.lambda.graphics.texture import com.lambda.graphics.texture.TextureUtils.bindTexture -import com.lambda.threading.mainThread -import com.lambda.threading.runGameScheduled import org.lwjgl.opengl.GL13.glGenTextures -abstract class Texture { - private val id = glGenTextures() +open class Texture { + val id = glGenTextures() fun bind(slot: Int = 0) = bindTexture(id, slot) } diff --git a/common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt b/common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt new file mode 100644 index 000000000..d95392d01 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt @@ -0,0 +1,74 @@ +package com.lambda.graphics.video + +import org.jcodec.api.FrameGrab +import org.jcodec.common.io.NIOUtils +import org.jcodec.common.model.ColorSpace +import org.jcodec.common.model.Picture +import org.jcodec.scale.ColorUtil +import org.jcodec.scale.RgbToBgr +import java.awt.image.BufferedImage +import java.awt.image.DataBufferByte +import java.io.File + +object JCodecUtils { + fun demuxVideo(path: String): FrameGrab = + FrameGrab.createFrameGrab(NIOUtils.readableChannel(File(path))) + + fun toBufferedImage(src: Picture): BufferedImage { + val processedSrc = convertToBGR(src) + val dst = BufferedImage(processedSrc.croppedWidth, processedSrc.croppedHeight, BufferedImage.TYPE_3BYTE_BGR) + + if (processedSrc.crop == null) { + copyImageData(processedSrc, dst) + } else { + copyCroppedImageData(processedSrc, dst) + } + + return dst + } + + private fun convertToBGR(src: Picture): Picture { + if (src.color == ColorSpace.BGR) return src + + val bgr = Picture.createCropped(src.width, src.height, ColorSpace.BGR, src.crop) + + if (src.color == ColorSpace.RGB) { + RgbToBgr().transform(src, bgr) + } else { + val transform = ColorUtil.getTransform(src.color, ColorSpace.RGB) + transform.transform(src, bgr) + RgbToBgr().transform(bgr, bgr) + } + + return bgr + } + + private fun copyImageData(src: Picture, dst: BufferedImage) { + val data = (dst.raster.dataBuffer as DataBufferByte).data + val srcData = src.getPlaneData(0) + + for (i in data.indices) { + data[i] = (srcData[i] + 128).toByte() + } + } + + private fun copyCroppedImageData(src: Picture, dst: BufferedImage) { + val data = (dst.raster.dataBuffer as DataBufferByte).data + val srcData = src.getPlaneData(0) + val dstStride = dst.width * 3 + val srcStride = src.width * 3 + + for (line in 0 until dst.height) { + var dstOffset = line * dstStride + var srcOffset = line * srcStride + + for (x in 0 until dstStride step 3) { + data[dstOffset] = (srcData[srcOffset] + 128).toByte() + data[dstOffset + 1] = (srcData[srcOffset + 1] + 128).toByte() + data[dstOffset + 2] = (srcData[srcOffset + 2] + 128).toByte() + dstOffset += 3 + srcOffset += 3 + } + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt new file mode 100644 index 000000000..beff72f0e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt @@ -0,0 +1,44 @@ +package com.lambda.graphics.video + +import com.lambda.graphics.buffer.BufferUsage +import com.lambda.graphics.buffer.pbo.PixelBuffer +import com.lambda.graphics.texture.Texture +import com.lambda.graphics.video.JCodecUtils.toBufferedImage +import com.pngencoder.PngEncoder +import java.io.File +import java.nio.ByteBuffer +import javax.imageio.ImageIO + + +class Video( + private val input: String, +) { + private val decoderStream = JCodecUtils.demuxVideo(input) + + val width = decoderStream.mediaInfo.dim.width + val height = decoderStream.mediaInfo.dim.height + + private val pbo = PixelBuffer(width, height, buffers = 2, bufferUsage = BufferUsage.STREAM) + val texture = Texture() + + fun upload() { + val picture = decoderStream.nativeFrame + if (picture == null) { + decoderStream.seekToFramePrecise(0) + return + } + + val image = toBufferedImage(picture) + + val bytes = PngEncoder() + .withBufferedImage(image) + .toBytes() + + val buffer = + ByteBuffer.allocateDirect(bytes.size) + .put(bytes) + .flip() + + pbo.mapTexture(texture.id, buffer) + } +} diff --git a/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt b/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt index aca17d474..6170d4cfb 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt @@ -2,7 +2,7 @@ package com.lambda.gui.impl import com.lambda.Lambda.mc import com.lambda.graphics.animation.Animation.Companion.exp -import com.lambda.graphics.buffer.FrameBuffer +import com.lambda.graphics.buffer.fbo.FrameBuffer import com.lambda.graphics.shader.Shader import com.lambda.gui.AbstractGuiConfigurable import com.lambda.gui.GuiConfigurable @@ -10,17 +10,13 @@ import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.LambdaGui import com.lambda.gui.api.component.WindowComponent import com.lambda.gui.api.component.core.list.ChildLayer -import com.lambda.gui.impl.clickgui.LambdaClickGui import com.lambda.gui.impl.clickgui.buttons.SettingButton import com.lambda.gui.impl.clickgui.windows.ModuleWindow import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow import com.lambda.gui.impl.clickgui.windows.tag.TagWindow -import com.lambda.gui.impl.hudgui.LambdaHudGui import com.lambda.module.Module import com.lambda.module.modules.client.ClickGui import com.lambda.util.Mouse -import com.mojang.blaze3d.systems.RenderSystem.recordRenderCall -import kotlin.reflect.KMutableProperty import kotlin.reflect.KMutableProperty0 abstract class AbstractClickGui(name: String, owner: Module? = null) : LambdaGui(name, owner) { @@ -128,4 +124,4 @@ abstract class AbstractClickGui(name: String, owner: Module? = null) : LambdaGui if (!isOpen) return closing = true } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt b/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt new file mode 100644 index 000000000..cf3a7ac83 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt @@ -0,0 +1,22 @@ +package com.lambda.module.hud + +import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture +import com.lambda.graphics.video.Video +import com.lambda.module.HudModule + +object VideoTest : HudModule( + name = "VideoTest", + defaultTags = setOf(), +) { + override val width = 430.0 + override val height = 548.0 + + private val video = Video("C:\\Users\\Kamigen\\Documents\\weed.mp4") + + init { + onRender { + video.upload() + drawTexture(video.texture, rect) + } + } +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 8844cc10f..d0d60bf8e 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.internal.jvm.Jvm + val modVersion: String by project val minecraftVersion: String by project val fabricLoaderVersion: String by project @@ -68,6 +70,7 @@ dependencies { includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") + includeLib("org.jcodec:jcodec:0.2.3") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") @@ -100,4 +103,29 @@ tasks { // They allow you to make field, method, and class access public. injectAccessWidener = true } + + register("run + RenderDoc") { + val javaHome = Jvm.current().javaHome + + commandLine = listOf( + "C:\\Program Files\\RenderDoc\\renderdoccmd.exe", + "capture", + "--opt-api-validation", + "--opt-api-validation-unmute", + "--opt-hook-children", + "--wait-for-exit", + "--working-dir", + ".", + "$javaHome/bin/java.exe", + "-Xmx64m", + "-Xms64m", + //"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005", + "-Dorg.gradle.appname=gradlew", + "-Dorg.gradle.java.home=$javaHome", + "-classpath", + "C:\\Users\\Kamigen\\Desktop\\BetterElytraBot\\NeoLambda\\gradle\\wrapper\\gradle-wrapper.jar", + "org.gradle.wrapper.GradleWrapperMain", + ":fabric:runClient", + ) + } } diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 935fa55ed..bf6133cdf 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -82,6 +82,7 @@ dependencies { includeLib("org.javassist:javassist:3.28.0-GA") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") + includeLib("org.jcodec:jcodec:0.2.3") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 9978da288..4693ae091 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") + includeLib("org.jcodec:jcodec:0.2.3") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge-neoforge:$kotlinForgeVersion") From 06aba2d8aa6ea37cc2f37f906ca0136062eb1ff4 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:35:03 -0400 Subject: [PATCH 08/45] added upload record time --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 88 +++++++++---------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index 0c5971dbe..e10bf7cd5 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -1,10 +1,8 @@ package com.lambda.graphics.buffer.pbo import com.lambda.graphics.buffer.BufferUsage -import com.lambda.graphics.texture.TextureUtils.setupTexture import org.lwjgl.opengl.GL45C.* import java.nio.ByteBuffer -import kotlin.system.measureNanoTime class PixelBuffer( private val width: Int, @@ -16,62 +14,58 @@ class PixelBuffer( 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 - fun mapTexture(id: Int, buffer: ByteBuffer) { - val time = measureNanoTime { - upload(buffer) { allocate -> - // Bind the texture - glBindTexture(GL_TEXTURE_2D, id) - - if (allocate) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - - // Allocate the texture memory - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) - } - else { - // Update the texture - glTextureSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) - } - } + private val queryId = glGenQueries() // Used to measure the time taken to upload data to the PBO + val uploadTime get() = IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }[0] + + fun mapTexture(id: Int, buffer: ByteBuffer) = + upload(buffer) { + // Bind the texture + glBindTexture(GL_TEXTURE_2D, id) + + // Perform the actual data transfer to the GPU + glTextureSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) } - println("PBO => texture $id took $time nanoseconds") - } + fun upload(data: ByteBuffer, process: () -> Unit) = + recordTransfer { + uploadIdx = (writeIdx + 1) % buffers - fun upload(data: ByteBuffer, process: (Boolean) -> Unit) { - uploadIdx = (writeIdx + 1) % buffers + // Bind the next PBO to update pixel values + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) - // Bind the next PBO to update pixel values - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) + // Map the buffer into the memory + val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) + if (bufferData != null) { + bufferData.put(data) - // Allocate the memory for the buffer - process(true) + // Release the buffer + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) + } else { + println("Failed to map the PBO") + } - // Map the buffer into the memory - val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) - if (bufferData != null) { - bufferData.put(data) + // Bind the current PBO for writing + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) - // Release the buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) - } else { - println("Failed to map the PBO") - } + // Copy the pixel values from the PBO to the texture + process() - // Bind the current PBO for writing - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) + // Unbind the PBO + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) - // Copy the pixel values from the PBO to the texture - process(false) + // Swap the indices + writeIdx = uploadIdx + } - // Unbind the PBO - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + private fun recordTransfer(block: () -> Unit) { + // Start the timer + glBeginQuery(GL_TIME_ELAPSED, queryId) - println("Uploaded data to PBO $uploadIdx at $bufferData") + // Perform the transfer + block() - // Swap the indices - writeIdx = uploadIdx + // Stop the timer + glEndQuery(GL_TIME_ELAPSED) } // Called when no references to the object exist @@ -89,7 +83,7 @@ class PixelBuffer( // 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, GL_STREAM_DRAW) + glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl) } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Unbind the buffer From 68b24da1629bf9eb621fec4cd2701bbd44eacd4a Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:45:25 -0400 Subject: [PATCH 09/45] added debug gremedy labels --- .../kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index e10bf7cd5..7f7a8cd3b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -2,6 +2,7 @@ package com.lambda.graphics.buffer.pbo import com.lambda.graphics.buffer.BufferUsage import org.lwjgl.opengl.GL45C.* +import org.lwjgl.opengl.GREMEDYStringMarker import java.nio.ByteBuffer class PixelBuffer( @@ -30,6 +31,8 @@ class PixelBuffer( recordTransfer { uploadIdx = (writeIdx + 1) % buffers + GREMEDYStringMarker.glStringMarkerGREMEDY("Data transfer to buffer") + // Bind the next PBO to update pixel values glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) @@ -47,6 +50,8 @@ class PixelBuffer( // Bind the current PBO for writing glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) + GREMEDYStringMarker.glStringMarkerGREMEDY("Data transfer to GPU") + // Copy the pixel values from the PBO to the texture process() From 12e2045b5697e6570ae84af4ed78a368e43f4614 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:40:26 -0400 Subject: [PATCH 10/45] better pbo --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 73 ++++++++++++++----- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index 7f7a8cd3b..c743e104f 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -1,8 +1,9 @@ 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 org.lwjgl.opengl.GREMEDYStringMarker import java.nio.ByteBuffer class PixelBuffer( @@ -17,24 +18,51 @@ class PixelBuffer( private val queryId = glGenQueries() // Used to measure the time taken to upload data to the PBO val uploadTime get() = IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }[0] + var transferRate = 0L // The transfer rate in bytes per second + private set + + var pboSupported = false + private set fun mapTexture(id: Int, buffer: ByteBuffer) = upload(buffer) { // Bind the texture glBindTexture(GL_TEXTURE_2D, id) - // Perform the actual data transfer to the GPU - glTextureSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + 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 + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + } + else { + // Perform the actual data transfer to the GPU + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer) + } } fun upload(data: ByteBuffer, process: () -> Unit) = recordTransfer { - uploadIdx = (writeIdx + 1) % buffers + if (buffers >= 2) + uploadIdx = (writeIdx + 1) % buffers - GREMEDYStringMarker.glStringMarkerGREMEDY("Data transfer to buffer") + // Copy the pixel values from the PBO to the texture + process() - // Bind the next PBO to update pixel values - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) + if (!pboSupported) return@recordTransfer + + // 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) @@ -43,17 +71,7 @@ class PixelBuffer( // Release the buffer glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) - } else { - println("Failed to map the PBO") - } - - // Bind the current PBO for writing - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) - - GREMEDYStringMarker.glStringMarkerGREMEDY("Data transfer to GPU") - - // Copy the pixel values from the PBO to the texture - process() + } else throw IllegalStateException("Failed to map the buffer") // Unbind the PBO glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) @@ -63,6 +81,11 @@ class PixelBuffer( } private fun recordTransfer(block: () -> Unit) { + if (!pboSupported) { + block() + return + } + // Start the timer glBeginQuery(GL_TIME_ELAPSED, queryId) @@ -71,6 +94,12 @@ class PixelBuffer( // 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 + } } // Called when no references to the object exist @@ -80,7 +109,13 @@ class PixelBuffer( } init { - if (buffers < 1) throw IllegalArgumentException("Buffers must be greater than or equal to 1") + // Check if the PBO is supported + GL.getCapabilities().let { pboSupported = it.OpenGL30 || it.GL_ARB_pixel_buffer_object } + + 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") + + if (buffers < 0) throw IllegalArgumentException("Buffers must be greater than or equal to 0") // Generate the PBOs glGenBuffers(pboIds) From 7cbfd1443a92e8134fb10a98c1ca6d8fac63474c Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:56:18 -0400 Subject: [PATCH 11/45] better handling --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 49 ++++++++++--------- .../kotlin/com/lambda/module/hud/VideoTest.kt | 4 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index c743e104f..89b2bd345 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -17,14 +17,24 @@ class PixelBuffer( 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 - val uploadTime get() = IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }[0] - var transferRate = 0L // The transfer rate in bytes per second - private set + private val uploadTime get() = IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }[0] + private var transferRate = 0L // The transfer rate in bytes per second - var pboSupported = false - private set + private val pboSupported = GL.getCapabilities().OpenGL30 || GL.getCapabilities().GL_ARB_pixel_buffer_object + + private var initialDataSent: Boolean = false + + 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 + } - fun mapTexture(id: Int, buffer: ByteBuffer) = upload(buffer) { // Bind the texture glBindTexture(GL_TEXTURE_2D, id) @@ -34,13 +44,20 @@ class PixelBuffer( glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) // Perform the actual data transfer to the GPU - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + 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 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer) + 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) } + } fun upload(data: ByteBuffer, process: () -> Unit) = recordTransfer { @@ -50,8 +67,6 @@ class PixelBuffer( // Copy the pixel values from the PBO to the texture process() - if (!pboSupported) return@recordTransfer - // Bind the current PBO for writing glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) @@ -81,11 +96,6 @@ class PixelBuffer( } private fun recordTransfer(block: () -> Unit) { - if (!pboSupported) { - block() - return - } - // Start the timer glBeginQuery(GL_TIME_ELAPSED, queryId) @@ -97,9 +107,7 @@ class PixelBuffer( // Calculate the transfer rate val time = uploadTime - if (time > 0) { - transferRate = (width * height * 4L * 1_000_000_000) / time - } + if (time > 0) transferRate = (width * height * 4L * 1_000_000_000) / time } // Called when no references to the object exist @@ -109,14 +117,11 @@ class PixelBuffer( } init { - // Check if the PBO is supported - GL.getCapabilities().let { pboSupported = it.OpenGL30 || it.GL_ARB_pixel_buffer_object } + 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") - if (buffers < 0) throw IllegalArgumentException("Buffers must be greater than or equal to 0") - // Generate the PBOs glGenBuffers(pboIds) diff --git a/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt b/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt index cf3a7ac83..b0e25a88c 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt @@ -15,8 +15,8 @@ object VideoTest : HudModule( init { onRender { - video.upload() - drawTexture(video.texture, rect) + //video.upload() + //drawTexture(video.texture, rect) } } } From a130c8c34fa4c4aaff0d086696f0d38141f5da6d Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:59:35 -0400 Subject: [PATCH 12/45] removed videos --- common/build.gradle.kts | 1 - .../com/lambda/graphics/video/JCodecUtils.kt | 74 ------------------- .../kotlin/com/lambda/graphics/video/Video.kt | 44 ----------- .../kotlin/com/lambda/module/hud/VideoTest.kt | 22 ------ fabric/build.gradle.kts | 26 ------- forge/build.gradle.kts | 1 - neoforge/build.gradle.kts | 1 - 7 files changed, 169 deletions(-) delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/Video.kt delete mode 100644 common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index eb6433c76..07481fb66 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -24,7 +24,6 @@ dependencies { implementation("org.reflections:reflections:0.10.2") implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") implementation("com.pngencoder:pngencoder:0.15.0") - implementation("org.jcodec:jcodec:0.2.3") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") diff --git a/common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt b/common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt deleted file mode 100644 index d95392d01..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/JCodecUtils.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.lambda.graphics.video - -import org.jcodec.api.FrameGrab -import org.jcodec.common.io.NIOUtils -import org.jcodec.common.model.ColorSpace -import org.jcodec.common.model.Picture -import org.jcodec.scale.ColorUtil -import org.jcodec.scale.RgbToBgr -import java.awt.image.BufferedImage -import java.awt.image.DataBufferByte -import java.io.File - -object JCodecUtils { - fun demuxVideo(path: String): FrameGrab = - FrameGrab.createFrameGrab(NIOUtils.readableChannel(File(path))) - - fun toBufferedImage(src: Picture): BufferedImage { - val processedSrc = convertToBGR(src) - val dst = BufferedImage(processedSrc.croppedWidth, processedSrc.croppedHeight, BufferedImage.TYPE_3BYTE_BGR) - - if (processedSrc.crop == null) { - copyImageData(processedSrc, dst) - } else { - copyCroppedImageData(processedSrc, dst) - } - - return dst - } - - private fun convertToBGR(src: Picture): Picture { - if (src.color == ColorSpace.BGR) return src - - val bgr = Picture.createCropped(src.width, src.height, ColorSpace.BGR, src.crop) - - if (src.color == ColorSpace.RGB) { - RgbToBgr().transform(src, bgr) - } else { - val transform = ColorUtil.getTransform(src.color, ColorSpace.RGB) - transform.transform(src, bgr) - RgbToBgr().transform(bgr, bgr) - } - - return bgr - } - - private fun copyImageData(src: Picture, dst: BufferedImage) { - val data = (dst.raster.dataBuffer as DataBufferByte).data - val srcData = src.getPlaneData(0) - - for (i in data.indices) { - data[i] = (srcData[i] + 128).toByte() - } - } - - private fun copyCroppedImageData(src: Picture, dst: BufferedImage) { - val data = (dst.raster.dataBuffer as DataBufferByte).data - val srcData = src.getPlaneData(0) - val dstStride = dst.width * 3 - val srcStride = src.width * 3 - - for (line in 0 until dst.height) { - var dstOffset = line * dstStride - var srcOffset = line * srcStride - - for (x in 0 until dstStride step 3) { - data[dstOffset] = (srcData[srcOffset] + 128).toByte() - data[dstOffset + 1] = (srcData[srcOffset + 1] + 128).toByte() - data[dstOffset + 2] = (srcData[srcOffset + 2] + 128).toByte() - dstOffset += 3 - srcOffset += 3 - } - } - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt deleted file mode 100644 index beff72f0e..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.lambda.graphics.video - -import com.lambda.graphics.buffer.BufferUsage -import com.lambda.graphics.buffer.pbo.PixelBuffer -import com.lambda.graphics.texture.Texture -import com.lambda.graphics.video.JCodecUtils.toBufferedImage -import com.pngencoder.PngEncoder -import java.io.File -import java.nio.ByteBuffer -import javax.imageio.ImageIO - - -class Video( - private val input: String, -) { - private val decoderStream = JCodecUtils.demuxVideo(input) - - val width = decoderStream.mediaInfo.dim.width - val height = decoderStream.mediaInfo.dim.height - - private val pbo = PixelBuffer(width, height, buffers = 2, bufferUsage = BufferUsage.STREAM) - val texture = Texture() - - fun upload() { - val picture = decoderStream.nativeFrame - if (picture == null) { - decoderStream.seekToFramePrecise(0) - return - } - - val image = toBufferedImage(picture) - - val bytes = PngEncoder() - .withBufferedImage(image) - .toBytes() - - val buffer = - ByteBuffer.allocateDirect(bytes.size) - .put(bytes) - .flip() - - pbo.mapTexture(texture.id, buffer) - } -} diff --git a/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt b/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt deleted file mode 100644 index b0e25a88c..000000000 --- a/common/src/main/kotlin/com/lambda/module/hud/VideoTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.lambda.module.hud - -import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture -import com.lambda.graphics.video.Video -import com.lambda.module.HudModule - -object VideoTest : HudModule( - name = "VideoTest", - defaultTags = setOf(), -) { - override val width = 430.0 - override val height = 548.0 - - private val video = Video("C:\\Users\\Kamigen\\Documents\\weed.mp4") - - init { - onRender { - //video.upload() - //drawTexture(video.texture, rect) - } - } -} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index d0d60bf8e..e34c7b297 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -70,7 +70,6 @@ dependencies { includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - includeLib("org.jcodec:jcodec:0.2.3") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") @@ -103,29 +102,4 @@ tasks { // They allow you to make field, method, and class access public. injectAccessWidener = true } - - register("run + RenderDoc") { - val javaHome = Jvm.current().javaHome - - commandLine = listOf( - "C:\\Program Files\\RenderDoc\\renderdoccmd.exe", - "capture", - "--opt-api-validation", - "--opt-api-validation-unmute", - "--opt-hook-children", - "--wait-for-exit", - "--working-dir", - ".", - "$javaHome/bin/java.exe", - "-Xmx64m", - "-Xms64m", - //"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005", - "-Dorg.gradle.appname=gradlew", - "-Dorg.gradle.java.home=$javaHome", - "-classpath", - "C:\\Users\\Kamigen\\Desktop\\BetterElytraBot\\NeoLambda\\gradle\\wrapper\\gradle-wrapper.jar", - "org.gradle.wrapper.GradleWrapperMain", - ":fabric:runClient", - ) - } } diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index bf6133cdf..935fa55ed 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -82,7 +82,6 @@ dependencies { includeLib("org.javassist:javassist:3.28.0-GA") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - includeLib("org.jcodec:jcodec:0.2.3") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 4693ae091..9978da288 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -69,7 +69,6 @@ dependencies { includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - includeLib("org.jcodec:jcodec:0.2.3") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge-neoforge:$kotlinForgeVersion") From bbd705f4a4d68bf97a4800e2e72fbef8cdb2d7b2 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:04:38 -0400 Subject: [PATCH 13/45] added documentation for pbo --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index 89b2bd345..0dc03a38e 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -6,6 +6,15 @@ 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, @@ -24,6 +33,12 @@ class PixelBuffer( 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) @@ -59,6 +74,12 @@ class PixelBuffer( } } + /** + * 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) @@ -95,6 +116,11 @@ class PixelBuffer( 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) @@ -110,12 +136,19 @@ class PixelBuffer( if (time > 0) transferRate = (width * height * 4L * 1_000_000_000) / time } - // Called when no references to the object exist + /** + * 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") From 4b965566af0a3841d873645462615bd9cfb08022 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:06:09 -0400 Subject: [PATCH 14/45] moved the fbo down one level --- common/src/main/kotlin/com/lambda/graphics/RenderMain.kt | 2 +- .../kotlin/com/lambda/graphics/buffer/{fbo => }/FrameBuffer.kt | 3 ++- common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename common/src/main/kotlin/com/lambda/graphics/buffer/{fbo => }/FrameBuffer.kt (99%) diff --git a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt index 6eabe8442..998a21872 100644 --- a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -7,7 +7,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.graphics.animation.AnimationTicker -import com.lambda.graphics.buffer.fbo.FrameBuffer +import com.lambda.graphics.buffer.FrameBuffer import com.lambda.graphics.gl.GlStateUtils.setupGL import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.resetMatrices diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/fbo/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt similarity index 99% rename from common/src/main/kotlin/com/lambda/graphics/buffer/fbo/FrameBuffer.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt index 25b3bd3bc..db164955a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/fbo/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt @@ -1,4 +1,5 @@ -package com.lambda.graphics.buffer.fbo +package com.lambda.graphics.buffer + import com.lambda.Lambda.mc import com.lambda.graphics.RenderMain import com.lambda.graphics.buffer.vao.VAO diff --git a/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt b/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt index 6170d4cfb..d2643785f 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt @@ -2,7 +2,7 @@ package com.lambda.gui.impl import com.lambda.Lambda.mc import com.lambda.graphics.animation.Animation.Companion.exp -import com.lambda.graphics.buffer.fbo.FrameBuffer +import com.lambda.graphics.buffer.FrameBuffer import com.lambda.graphics.shader.Shader import com.lambda.gui.AbstractGuiConfigurable import com.lambda.gui.GuiConfigurable From 895a069cc203a038c01fafb76cca1cf96a6fe7a5 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:13:31 -0400 Subject: [PATCH 15/45] removed loader phase time measure --- common/src/main/kotlin/com/lambda/core/Loader.kt | 9 +-------- .../graphics/renderer/gui/font/glyph/EmojiGlyphs.kt | 5 ++--- .../graphics/renderer/gui/font/glyph/FontGlyphs.kt | 5 ++--- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index 271143d2c..f2bb399f4 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -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)") diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt index 0405a0dcf..aa07d686c 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt @@ -16,7 +16,6 @@ import javax.imageio.ImageIO import kotlin.math.ceil import kotlin.math.log2 import kotlin.math.sqrt -import kotlin.system.measureTimeMillis import kotlin.time.Duration.Companion.days class EmojiGlyphs(zipUrl: String) { @@ -28,8 +27,8 @@ class EmojiGlyphs(zipUrl: String) { init { runCatching { - val time = measureTimeMillis { downloadAndProcessZip(zipUrl) } - LOG.info("Loaded ${emojiMap.size} emojis in $time ms") + downloadAndProcessZip(zipUrl) + LOG.info("Loaded ${emojiMap.size} emojis") }.onFailure { LOG.error("Failed to load emojis: ${it.message}", it) fontTexture = MipmapTexture(BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB)) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt index 3c492b26b..3461ec2af 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt @@ -11,7 +11,6 @@ import java.awt.Font import java.awt.Graphics2D import java.awt.image.BufferedImage import kotlin.math.max -import kotlin.system.measureTimeMillis class FontGlyphs( private val font: Font @@ -23,8 +22,8 @@ class FontGlyphs( init { runCatching { - val time = measureTimeMillis { processGlyphs() } - LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters in $time ms") + processGlyphs() + LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters") }.onFailure { LOG.error("Failed to load font glyphs: ${it.message}", it) fontTexture = MipmapTexture(BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB)) From 0644c69868193a7027efc22396722fabddf7d9f6 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 24 Aug 2024 08:46:59 -0400 Subject: [PATCH 16/45] Update build.gradle.kts --- fabric/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index e34c7b297..8844cc10f 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.internal.jvm.Jvm - val modVersion: String by project val minecraftVersion: String by project val fabricLoaderVersion: String by project From de3bb6e5651ca6b34603a508d3e7fd8c2e585ae4 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 25 Aug 2024 05:02:50 +0200 Subject: [PATCH 17/45] TickEvent KDocs --- .../lambda/mixin/MinecraftClientMixin.java | 4 +- .../com/lambda/event/events/TickEvent.kt | 86 ++++++++++++++----- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 6c92d6558..809a077c4 100644 --- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -37,12 +37,12 @@ void onTickPost(CallbackInfo ci) { @Inject(method = "render", at = @At("HEAD")) void onLoopTickPre(CallbackInfo ci) { - EventFlow.post(new TickEvent.GameLoop.Pre()); + EventFlow.post(new TickEvent.Render.Pre()); } @Inject(method = "render", at = @At("RETURN")) void onLoopTickPost(CallbackInfo ci) { - EventFlow.post(new TickEvent.GameLoop.Post()); + 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") diff --git a/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt b/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt index d47597fdb..003f956d9 100644 --- a/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/TickEvent.kt @@ -1,58 +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 tick 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 tick 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 on each tick of the game loop. + * 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 GameLoop : TickEvent() { + abstract class Render : TickEvent() { /** - * A class representing a [TickEvent.Player] that is triggered before each tick of the game loop. + * Triggered before each render tick ([TickEvent.Render]) of the game loop. */ class Pre : TickEvent() /** - * A class representing a [TickEvent.Player] that is triggered after each tick of the game loop. + * Triggered after each render tick ([TickEvent.Render]) of the game loop. */ class Post : TickEvent() } /** - * A class representing a [TickEvent] that is triggered when the player gets ticked. + * 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() } From b40b1aef9d0f1aa12097d3478f5c8e4951a012fc Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 25 Aug 2024 05:16:01 +0200 Subject: [PATCH 18/45] Smol refac --- .../kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index 0dc03a38e..17e815600 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -26,7 +26,8 @@ class PixelBuffer( 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) }[0] + 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 @@ -60,8 +61,7 @@ class PixelBuffer( // 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 { + } 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) } From dcf244df4eafbf389aafb8917d883a5c1b44dce6 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 25 Aug 2024 05:28:26 +0200 Subject: [PATCH 19/45] Better logging and remove texture settings --- .../com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt | 2 +- .../lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt | 3 ++- .../kotlin/com/lambda/interaction/PlayerPacketManager.kt | 1 - .../com/lambda/module/modules/client/RenderSettings.kt | 5 ----- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt index 8d8f85f48..cfe21c3d5 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt @@ -17,7 +17,7 @@ enum class LambdaEmoji(private val zipUrl: String) { object Loader : Loadable { override fun load(): String { entries.forEach(LambdaEmoji::loadGlyphs) - return "Loaded ${entries.size} emoji sets" + return "Loaded ${entries.size} emoji sets with a total of ${entries.sumOf { it.glyphs.count }} emojis" } } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt index aa07d686c..6e2dd4e05 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt @@ -25,10 +25,11 @@ class EmojiGlyphs(zipUrl: String) { private lateinit var image: BufferedImage private lateinit var graphics: Graphics2D + val count get() = emojiMap.size + init { runCatching { downloadAndProcessZip(zipUrl) - LOG.info("Loaded ${emojiMap.size} emojis") }.onFailure { LOG.error("Failed to load emojis: ${it.message}", it) fontTexture = MipmapTexture(BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB)) diff --git a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt index 491ca07bb..1a6e95e6d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -5,7 +5,6 @@ import com.lambda.core.Loadable import com.lambda.event.EventFlow.post import com.lambda.event.EventFlow.postChecked import com.lambda.event.events.PlayerPacketEvent -import com.lambda.interaction.rotation.Rotation.Companion.fixSensitivity import com.lambda.threading.runSafe import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.VecUtils.approximate diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt index ec29fad21..099dbdafb 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt @@ -18,10 +18,6 @@ object RenderSettings : Module( val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font } private val lodBiasSetting by setting("Smoothing", 0.0, -10.0..10.0, 0.5) { page == Page.Font } - // Texture - val textureCompression by setting("Compression", 1, 1..9, 1, description = "Texture compression level, higher is slower") { page == Page.TEXTURE } - val threadedCompression by setting("Threaded Compression", false, description = "Use multiple threads for texture compression") { page == Page.TEXTURE } - // ESP val uploadsPerTick by setting("Uploads", 16, 1..256, 1, unit = " chunk/tick") { page == Page.ESP } val rebuildsPerTick by setting("Rebuilds", 64, 1..256, 1, unit = " chunk/tick") { page == Page.ESP } @@ -32,7 +28,6 @@ object RenderSettings : Module( private enum class Page { Font, - TEXTURE, ESP, } } From c97a29c0c25f6c3cbe8cde2b4710a57802c32273 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 25 Aug 2024 05:39:07 +0200 Subject: [PATCH 20/45] Introduce constants for texture options --- .../com/lambda/graphics/texture/TextureUtils.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt index 5ed6289cb..d3a6f94e8 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt @@ -1,6 +1,5 @@ package com.lambda.graphics.texture -import com.lambda.module.modules.client.RenderSettings import com.mojang.blaze3d.systems.RenderSystem import com.pngencoder.PngEncoder import net.minecraft.client.texture.NativeImage @@ -8,15 +7,17 @@ import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL45C.* import java.awt.* import java.awt.image.BufferedImage -import java.io.ByteArrayOutputStream import kotlin.math.roundToInt import kotlin.math.sqrt object TextureUtils { + private const val COMPRESSION_LEVEL = 1 + private const val THREADED_COMPRESSION = false + private val metricCache = mutableMapOf() private val encoderPreset = PngEncoder() - .withCompressionLevel(RenderSettings.textureCompression) - .withMultiThreadedCompressionEnabled(RenderSettings.threadedCompression) + .withCompressionLevel(COMPRESSION_LEVEL) + .withMultiThreadedCompressionEnabled(THREADED_COMPRESSION) fun bindTexture(id: Int, slot: Int = 0) { RenderSystem.activeTexture(GL_TEXTURE0 + slot) @@ -77,7 +78,7 @@ object TextureUtils { if (!font.canDisplay(codePoint)) return null val fontMetrics = metricCache.getOrPut(font) { - val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB) + val image = BufferedImage(COMPRESSION_LEVEL, COMPRESSION_LEVEL, BufferedImage.TYPE_INT_ARGB) val graphics2D = image.createGraphics() graphics2D.font = font From 47dbf6c204791eaf89073caa81b8972c54293823 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:15:04 -0400 Subject: [PATCH 21/45] test: video rendering --- common/build.gradle.kts | 1 + .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 169 ++++++------- .../graphics/renderer/gui/TextureRenderer.kt | 7 +- .../com/lambda/graphics/texture/Texture.kt | 2 +- .../lambda/graphics/texture/TextureUtils.kt | 12 +- .../com/lambda/graphics/video/AVUtils.kt | 239 ++++++++++++++++++ .../kotlin/com/lambda/graphics/video/Video.kt | 70 +++++ .../com/lambda/module/hud/PBOExample.kt | 27 ++ fabric/build.gradle.kts | 1 + forge/build.gradle.kts | 1 + 10 files changed, 428 insertions(+), 101 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/Video.kt create mode 100644 common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index d638a36a1..3923c87a4 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { implementation("org.reflections:reflections:0.10.2") implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") implementation("com.pngencoder:pngencoder:0.15.0") + implementation("org.bytedeco:ffmpeg-platform:6.1.1-1.5.10") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index 17e815600..d117665c7 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -1,119 +1,110 @@ package com.lambda.graphics.buffer.pbo -import com.lambda.Lambda.LOG import com.lambda.graphics.buffer.BufferUsage +import com.lambda.threading.runGameScheduled +import net.minecraft.block.entity.HopperBlockEntity.transfer +import net.minecraft.structure.StructureTemplate.process import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL45C.* +import org.lwjgl.system.MemoryUtil +import org.lwjgl.system.libc.LibCString.memcpy 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. + * This class manages the creation, usage, and cleanup of PBOs and provides methods to upload and download data efficiently. * - * @property width The width of the texture in pixels. - * @property height The height of the texture in pixels. + * - **Process**: + * Every function that performs a pixel transfer operation can use buffer objects instead of client memory. + * Functions that perform an upload operation, a pixel unpack, will use the buffer object bound to the target GL_PIXEL_UNPACK_BUFFER. + * Functions that perform a download operation, a pixel pack, will use the buffer object bound to the GL_PIXEL_PACK_BUFFER. + * These functions only use buffer objects if one is bound to that particular binding point when the function is called. + * If a buffer is bound, then the pointer value that those functions take is not a pointer, but an offset from the beginning of that buffer. + * + * @property size The size of the buffer(s). * @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.). + * @property init Code to run during the initialisation process. + * + * @see Pixel Buffer Object */ class PixelBuffer( - private val width: Int, - private val height: Int, + private val size: Long, private val buffers: Int = 2, private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC, + private val init: () -> Unit = {}, ) { - 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 pboIds = IntArray(buffers).apply { glGenBuffers(this) } + private var writeIdx = 0 // Buffer being filled by the CPU + private var uploadIdx = 0 // Buffer to transfer data to the GPU - private val pboSupported = GL.getCapabilities().OpenGL30 || GL.getCapabilities().GL_ARB_pixel_buffer_object + private val pboSupported = GL.getCapabilities().OpenGL15 || GL.getCapabilities().GL_ARB_pixel_buffer_object - private var initialDataSent: Boolean = false + private val queries = IntArray(2).apply { glGenQueries(this) } + private var transferRate = 0L // The transfer rate in bytes per second + private val uploadTime get() = IntArray(1).apply { glGetQueryObjectiv(queries[0], GL_QUERY_RESULT, this) }.first() + private val downloadTime get() = IntArray(1).apply { glGetQueryObjectiv(queries[1], GL_QUERY_RESULT, this) }.first() /** - * 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. + * Creates a new PBO use context required for everything related in this class. */ - 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) - } + fun use(block: PixelBuffer.() -> Unit) { + if (buffers >= 2) + uploadIdx = (writeIdx + 1) % buffers - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + // Do the main stuff + block() - // Unbind the texture - glBindTexture(GL_TEXTURE_2D, 0) - } + // Swap the indices + writeIdx = uploadIdx } /** * 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 + fun upload(data: ByteBuffer, transfer: () -> Unit = {}) = + recordTransfer(queries[0]) { + // Bind the current PBO for uploading 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) + MemoryUtil.memCopy(data, bufferData) // Release the buffer glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) } else throw IllegalStateException("Failed to map the buffer") + // Process + transfer() + // Unbind the PBO glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + } + + /** + * Downloads the data from the PBO from OpenGL back to the client memory + */ + fun download(process: (ByteBuffer) -> Unit) = + recordTransfer(queries[1]) { + // Bind the current PBO for writing + glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[writeIdx]) - // Swap the indices - writeIdx = uploadIdx + // Map the buffer into the memory + val bufferData = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY, size, null) + if (bufferData != null) { + // Do something with the data + process(bufferData) + + // Release the buffer + glUnmapBuffer(GL_PIXEL_PACK_BUFFER) + } else throw IllegalStateException("Failed to map the buffer") + + // Unbind the PBO + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) } /** @@ -121,9 +112,9 @@ class PixelBuffer( * * @param block A lambda function representing the block of code where the transfer occurs. */ - private fun recordTransfer(block: () -> Unit) { + private fun recordTransfer(query: Int, block: () -> Unit) { // Start the timer - glBeginQuery(GL_TIME_ELAPSED, queryId) + glBeginQuery(GL_TIME_ELAPSED, query) // Perform the transfer block() @@ -132,38 +123,40 @@ class PixelBuffer( glEndQuery(GL_TIME_ELAPSED) // Calculate the transfer rate - val time = uploadTime - if (time > 0) transferRate = (width * height * 4L * 1_000_000_000) / time + val time = if (query == 0) uploadTime else downloadTime + if (time > 0) transferRate = (size * 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) + protected fun finalize() { + runGameScheduled { + glDeleteBuffers(pboIds) + glDeleteQueries(queries) + } } /** * Initializes the PBOs, allocates memory for them, and handles unsupported PBO scenarios. * - * @throws IllegalArgumentException If the number of buffers is less than 0. + * @throws IllegalArgumentException If the number of buffers is less than 1. + * @throws UnsupportedOperationException If the machine doesn't support PBOs. */ init { - if (buffers < 0) throw IllegalArgumentException("Buffers must be greater than or equal to 0") + if (buffers < 1) throw IllegalArgumentException("Buffers must be greater than 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") + if (!pboSupported) + throw UnsupportedOperationException("Client tried to utilize PBOs, but they are not supported on the machine.") - // Generate the PBOs - glGenBuffers(pboIds) + init() // 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_PACK_BUFFER, pboIds[it]) + glBufferData(GL_PIXEL_PACK_BUFFER, size, bufferUsage.gl) } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Unbind the buffer + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) // Unbind the buffer } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt index fa1c65ad1..a41c27637 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt @@ -6,6 +6,7 @@ import com.lambda.graphics.buffer.vao.vertex.VertexAttrib import com.lambda.graphics.buffer.vao.vertex.VertexMode import com.lambda.graphics.shader.Shader import com.lambda.graphics.texture.Texture +import com.lambda.graphics.video.Video import com.lambda.module.modules.client.GuiSettings import com.lambda.util.math.Rect import com.lambda.util.math.Vec2d @@ -16,8 +17,10 @@ object TextureRenderer { private val shader = Shader("renderer/pos_tex") private val shaderColored = Shader("renderer/pos_tex_shady") - fun drawTexture(texture: Texture, rect: Rect) { + fun drawTexture(texture: Texture, rect: Rect, block: () -> Unit = {}) { texture.bind() + block() + shader.use() drawInternal(rect) @@ -54,4 +57,4 @@ object TextureRenderer { vao.render() vao.clear() } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index ff5990c87..0745abb9d 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -1,7 +1,7 @@ package com.lambda.graphics.texture import com.lambda.graphics.texture.TextureUtils.bindTexture -import org.lwjgl.opengl.GL13.glGenTextures +import org.lwjgl.opengl.GL45C.* open class Texture { val id = glGenTextures() diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt index d3a6f94e8..495d64b9f 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt @@ -15,7 +15,8 @@ object TextureUtils { private const val THREADED_COMPRESSION = false private val metricCache = mutableMapOf() - private val encoderPreset = PngEncoder() + + val encoderPreset = PngEncoder() .withCompressionLevel(COMPRESSION_LEVEL) .withMultiThreadedCompressionEnabled(THREADED_COMPRESSION) @@ -28,15 +29,6 @@ object TextureUtils { val width = bufferedImage.width val height = bufferedImage.height - // Here we cannot use GL_UNSIGNED_INT_8_8_8_8_REV or GL_UNSIGNED_INT_8_8_8_8 - // because the RGBA values are affected by the machine's endianness. - // On little-endian machines, you would read the data as - // 0xAABBGGRR and on big-endian machines as 0xRRGGBBAA. - // The solution is to use GL_UNSIGNED_BYTE and swap the bytes - // manually if necessary. (We won't need to) - // - // GL_UNSIGNED_BYTE -> [RR, GG, BB, AA] - // Array of floats normalized to [0.0, 1.0] -> [R, G, B, A] glTexImage2D(GL_TEXTURE_2D, lod, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, readImage(bufferedImage)) setupTexture(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR) diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt new file mode 100644 index 000000000..62796bf99 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt @@ -0,0 +1,239 @@ +package com.lambda.graphics.video + +import com.lambda.Lambda.LOG +import com.lambda.graphics.gl.Memory.int +import net.minecraft.predicate.entity.DistancePredicate.y +import org.bytedeco.ffmpeg.avcodec.AVCodecContext +import org.bytedeco.ffmpeg.avformat.AVFormatContext + +import org.bytedeco.javacpp.* +import org.bytedeco.ffmpeg.avcodec.AVPacket +import org.bytedeco.ffmpeg.avutil.AVChannelLayout +import org.bytedeco.ffmpeg.avutil.AVFrame +import org.bytedeco.ffmpeg.global.avcodec.av_packet_unref +import org.bytedeco.ffmpeg.global.avcodec.avcodec_alloc_context3 +import org.bytedeco.ffmpeg.global.avcodec.avcodec_find_decoder +import org.bytedeco.ffmpeg.global.avcodec.avcodec_open2 +import org.bytedeco.ffmpeg.global.avcodec.avcodec_parameters_to_context +import org.bytedeco.ffmpeg.global.avcodec.avcodec_receive_frame +import org.bytedeco.ffmpeg.global.avcodec.avcodec_send_packet +import org.bytedeco.ffmpeg.global.avformat.* +import org.bytedeco.ffmpeg.global.avutil.* +import org.bytedeco.ffmpeg.global.swscale +import org.lwjgl.openal.AL10.alBufferData +import sun.security.krb5.Confounder.bytes +import java.io.FileOutputStream +import java.io.OutputStream + +/** + * Utility object for handling audio-visual (AV) operations using the FFmpeg library. + * Provides functions to create codec contexts, format contexts, and iterating through video frames. + * + * ## Resources for further understanding: + * - [FFmpeg Documentation](https://ffmpeg.org/documentation.html) + * - [JAVACV Documentation](https://bytedeco.org/) + * - [Understanding Video Decoding in FFmpeg](https://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html) + * - [FFmpeg's AVFormatContext and AVCodecContext](https://ffmpeg.org/doxygen/trunk/structAVFormatContext.html) + * - [Swscale Library for Image Scaling](https://ffmpeg.org/doxygen/trunk/group__libsws.html) + */ +object AVUtils { + + /** + * Creates an `AVCodecContext` and an `AVFormatContext` for the video file at the specified path. + * + * @param path The file path to the video file. + * @return A Triple containing an `AVCodecContext`, an `AVFormatContext` and the stream codec index, or `null` if the context creation fails. + * + * ### Detailed Explanation: + * This function opens the video file using FFmpeg's `avformat_open_input()` function to initialize the `AVFormatContext`. + * After opening the file, `avformat_find_stream_info()` extracts information about the streams (audio, video, etc.). + * The function searches for a video stream (type `AVMEDIA_TYPE_VIDEO`), and if found, initializes a codec context + * with `avcodec_alloc_context3()`. The stream's codec parameters are then copied to the codec context using + * `avcodec_parameters_to_context()`. + * If any errors occur during these steps, the error message is logged, and the function returns `null`. + */ + fun createContext( + path: String + ): Triple< + AVCodecContext, + AVFormatContext, + Int>? { + val errStr = ByteArray(1024) + var err = -1 + var streamIndex = -1 + val formatContext = AVFormatContext(null) + + err = avformat_open_input(formatContext, path, null, null) + if (err < 0) { + av_strerror(err, errStr, 1024) + LOG.error("Failed to open video file at $path: ${errStr.decodeToString()}") + return null + } + + err = avformat_find_stream_info(formatContext, null as PointerPointer<*>?) + if (err < 0) { + av_strerror(err, errStr, 1024) + LOG.error("Failed to find stream info for video file at $path: ${errStr.decodeToString()}") + return null + } + + av_dump_format(formatContext, 0, path, 0) + + for (i in 0 until formatContext.nb_streams()) { + if (formatContext.streams(i).codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) { + streamIndex = i + break + } + } + + if (streamIndex == -1) { + LOG.error("Video stream not found in file at $path") + return null + } + + // Allocate codec context and set codec parameters + val codecContext = avcodec_alloc_context3(null) + avcodec_parameters_to_context(codecContext, formatContext.streams(streamIndex).codecpar()) + + return Triple(codecContext, formatContext, streamIndex) + } + + /** + * Returns an iterator over the decoded video frames for the given codec and format context pair. + * + * @param ctx A Pair of `AVCodecContext` and `AVFormatContext` created from `createContext()`. + * @return An `Iterator` of `AVFrame` representing the video frames, or `null` if initialization fails. + * + * ### Detailed Explanation: + * This function sets up a decoding pipeline using FFmpeg's `avcodec_find_decoder()` and `avcodec_open2()` + * to find and initialize the video codec. It also allocates memory for `AVFrame` structures used for decoding. + * To convert the decoded frames into RGB format, the Swscale library is used (`sws_getContext()`), which scales + * and formats the raw frames into RGB using `sws_scale()`. The frames are yielded using a Kotlin iterator. + * + * The iterator reads video packets (`av_read_frame()`) and decodes them into frames using `avcodec_send_packet()` + * and `avcodec_receive_frame()`. The iterator limits the frame count to 5 as a demonstration, but this can be + * adapted for full video playback. + */ + fun frameIterator( + ctx: Triple + ): Iterator? { + var err = -1 + val pkt = AVPacket() + + val codecContext = ctx.first + val formatContext = ctx.second + val streamIndex = ctx.third + + // Find and initialize the decoder for the video stream + val codec = avcodec_find_decoder(codecContext.codec_id()) + if (codec == null) { + LOG.error("Unsupported codec for video file") + return null + } + + err = avcodec_open2(codecContext, codec, null as PointerPointer<*>?) + if (err < 0) { + LOG.error("Failed to open codec for video decoding") + return null + } + + val frame = av_frame_alloc() ?: return null + + val pFrameRGB = av_frame_alloc() ?: run { + LOG.error("Failed to allocate AV frame for RGB conversion") + return null + } + + val numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB0, codecContext.width(), codecContext.height(), 1) + val buffer = BytePointer(av_malloc(numBytes.toLong())) + + val sourcePixFmt = correctForDeprecatedPixelFormat(codecContext.pix_fmt()) + + // Initialize Swscale context for converting YUV to RGB + // This is required to pass the image data to OpenGL. + val swsCtx = swscale.sws_getContext( + codecContext.width(), + codecContext.height(), + sourcePixFmt, + codecContext.width(), + codecContext.height(), + AV_PIX_FMT_RGB0, + swscale.SWS_BILINEAR, + null, null, null as DoublePointer? + ) + + if (swsCtx == null) { + LOG.error("Failed to initialize Swscale context") + return null + } + + av_image_fill_arrays( + pFrameRGB.data(), + pFrameRGB.linesize(), + buffer, + AV_PIX_FMT_RGB0, + codecContext.width(), + codecContext.height(), + 1 + ) + + return iterator { + while (av_read_frame(formatContext, pkt) >= 0) { + if (pkt.stream_index() == streamIndex) { + avcodec_send_packet(codecContext, pkt) + err = avcodec_receive_frame(codecContext, frame) + } + + if (err >= 0) { + swscale.sws_scale( + swsCtx, + frame.data(), + frame.linesize(), + 0, + codecContext.height(), + pFrameRGB.data(), + pFrameRGB.linesize() + ) + + yield(pFrameRGB) + } + + av_packet_unref(pkt) + } + + av_frame_free(frame) // Clean up to prevent memory leak + } + } + + private fun correctForDeprecatedPixelFormat(pixFmt: Int): Int { + // Fix swscaler deprecated pixel format warning + // (YUVJ has been deprecated, change pixel format to regular YUV) + return when (pixFmt) { + AV_PIX_FMT_YUVJ420P -> AV_PIX_FMT_YUV420P + AV_PIX_FMT_YUVJ422P -> AV_PIX_FMT_YUV422P + AV_PIX_FMT_YUVJ444P -> AV_PIX_FMT_YUV444P + AV_PIX_FMT_YUVJ440P -> AV_PIX_FMT_YUV440P + else -> pixFmt + } + } + + fun saveFrame(pFrame: AVFrame, width: Int, height: Int, dest: String) { + // Open file + val pFile = FileOutputStream("$dest/output.ppm") + + // Write header + pFile.write("P6\n$width $height\n255\n".toByteArray()) + + // Write pixel data + val data = pFrame.data(0) + val bytes = ByteArray(width * 3) + val lineSize = pFrame.linesize(0) + for (y in 0L until height) { + data.position(y * lineSize).get(bytes) + pFile.write(bytes) + } + + // Close file + pFile.close() + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt new file mode 100644 index 000000000..c14440d29 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt @@ -0,0 +1,70 @@ +package com.lambda.graphics.video + +import com.lambda.graphics.buffer.BufferUsage +import com.lambda.graphics.buffer.pbo.PixelBuffer +import com.lambda.graphics.texture.Texture +import org.bytedeco.ffmpeg.avcodec.AVCodecContext +import org.bytedeco.ffmpeg.avutil.AVFrame +import org.lwjgl.opengl.GL45C.* +import java.lang.Thread.sleep +import java.nio.ByteBuffer + +class Video( + codecContext: AVCodecContext, + private val iterator: Iterator, +) : Texture() { + val width = codecContext.width() + val height = codecContext.height() + private val frameTime = 1 / codecContext.framerate().num() + + private val avFrame: AVFrame? get() = if (iterator.hasNext()) iterator.next() else null + private val buffer: ByteBuffer? get() = avFrame?.asByteBuffer() + + private val pbo = PixelBuffer(width * height * 4L, buffers = 1, bufferUsage = BufferUsage.DYNAMIC) { + glBindTexture(GL_TEXTURE_2D, id) + + // Allocate texture storage + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + } + + private val startTime = System.nanoTime() + private var lastTime = System.nanoTime() + private var delta = 0L + + fun transfer() { + pbo.use { + delta = System.nanoTime() - lastTime + + if (delta > frameTime) { + upload(buffer ?: return@use) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + } + + lastTime = System.nanoTime() + } + } + } + + companion object { + /** + * Retrieves a video from the resources folder. + * + * @param path The path to the image. + */ + fun fromResource(path: String): Video { + val ctx = AVUtils.createContext(path) ?: + throw IllegalStateException("Could not create context from path: $path") + + val iterator = AVUtils.frameIterator(ctx) ?: + throw IllegalStateException("Could not create frame iterator: $path") + + return Video(ctx.first, iterator) + } + } +} diff --git a/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt b/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt new file mode 100644 index 000000000..f363ab5c0 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt @@ -0,0 +1,27 @@ +package com.lambda.module.hud + +import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture +import com.lambda.graphics.video.Video +import com.lambda.module.HudModule +import com.lambda.module.tag.ModuleTag + +object PBOExample : HudModule( + name = "PBOExample", + description = "Test the pbo impl", + defaultTags = setOf(ModuleTag.CLIENT), +) { + override val width: Double + get() = video.width.toDouble() + override val height: Double + get() = video.height.toDouble() + + private val video = Video.fromResource("C:\\Users\\Kamigen\\Downloads\\pizza.mp4") + + init { + onRender { + drawTexture(video, rect) { + video.transfer() + } + } + } +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 8ba1a9616..ec13264ce 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -70,6 +70,7 @@ dependencies { includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") + includeLib("org.bytedeco:ffmpeg-platform:6.1.1-1.5.10") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index c912c05f7..720636e9a 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { includeLib("org.javassist:javassist:3.28.0-GA") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") + includeLib("org.bytedeco:ffmpeg-platform:6.1.1-1.5.10") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion") From 8014bd0520022e06f90232a4da919ac3e63aad96 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:53:20 -0400 Subject: [PATCH 22/45] feature: pbo synchronization --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 114 ++++++++---------- .../graphics/renderer/gui/TextureRenderer.kt | 4 +- .../kotlin/com/lambda/graphics/video/Video.kt | 10 +- .../com/lambda/module/hud/PBOExample.kt | 6 +- 4 files changed, 58 insertions(+), 76 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index d117665c7..ea51bc27d 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -2,12 +2,9 @@ package com.lambda.graphics.buffer.pbo import com.lambda.graphics.buffer.BufferUsage import com.lambda.threading.runGameScheduled -import net.minecraft.block.entity.HopperBlockEntity.transfer -import net.minecraft.structure.StructureTemplate.process import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL45C.* import org.lwjgl.system.MemoryUtil -import org.lwjgl.system.libc.LibCString.memcpy import java.nio.ByteBuffer /** @@ -31,32 +28,28 @@ import java.nio.ByteBuffer class PixelBuffer( private val size: Long, private val buffers: Int = 2, - private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC, + private val bufferUsage: BufferUsage = BufferUsage.STATIC, private val init: () -> Unit = {}, ) { private val pboIds = IntArray(buffers).apply { glGenBuffers(this) } - private var writeIdx = 0 // Buffer being filled by the CPU - private var uploadIdx = 0 // Buffer to transfer data to the GPU + private val fences = LongArray(buffers) // Synchronization objects + private var index = 0 - private val pboSupported = GL.getCapabilities().OpenGL15 || GL.getCapabilities().GL_ARB_pixel_buffer_object - - private val queries = IntArray(2).apply { glGenQueries(this) } - private var transferRate = 0L // The transfer rate in bytes per second - private val uploadTime get() = IntArray(1).apply { glGetQueryObjectiv(queries[0], GL_QUERY_RESULT, this) }.first() - private val downloadTime get() = IntArray(1).apply { glGetQueryObjectiv(queries[1], GL_QUERY_RESULT, this) }.first() + private val pboSupported = GL.getCapabilities().OpenGL15 /** * Creates a new PBO use context required for everything related in this class. */ fun use(block: PixelBuffer.() -> Unit) { - if (buffers >= 2) - uploadIdx = (writeIdx + 1) % buffers + if (buffers != 2) { + index = 0 + } // Do the main stuff block() - // Swap the indices - writeIdx = uploadIdx + // Swap buffers + index = (index + 1) % buffers } /** @@ -64,67 +57,61 @@ class PixelBuffer( * * @param data The [ByteBuffer] containing the pixel data to be uploaded. */ - fun upload(data: ByteBuffer, transfer: () -> Unit = {}) = - recordTransfer(queries[0]) { - // Bind the current PBO for uploading - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) + fun upload(data: ByteBuffer, transfer: () -> Unit = {}) { + // Wait for the previous PBO to finish if a fence exists + if (fences[index] != 0L) { + val ret = glClientWaitSync(fences[index], GL_SYNC_FLUSH_COMMANDS_BIT, 50000000) // 50 ms timeout + if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) { + glDeleteSync(fences[index]) + fences[index] = 0L + } + } - // Map the buffer into the memory - val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) - if (bufferData != null) { - MemoryUtil.memCopy(data, bufferData) + // Bind the current PBO for uploading + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) - // Release the buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) - } else throw IllegalStateException("Failed to map the buffer") + // Map the buffer into the client's memory + val bufferData = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT or GL_MAP_INVALIDATE_BUFFER_BIT) + if (bufferData != null) { + MemoryUtil.memCopy(data, bufferData) - // Process - transfer() + // 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) - } + // Process + transfer() + + // Insert a sync object to track when the GPU finishes reading from the PBO + fences[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) + + // Unbind the PBO + // Once bound with 0, all pixel operations behave normal ways. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + } /** * Downloads the data from the PBO from OpenGL back to the client memory */ - fun download(process: (ByteBuffer) -> Unit) = - recordTransfer(queries[1]) { - // Bind the current PBO for writing - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[writeIdx]) + fun download(process: (ByteBuffer) -> Unit): Throwable? { + // Bind the current PBO for writing + glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[index]) - // Map the buffer into the memory - val bufferData = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY, size, null) - if (bufferData != null) { - // Do something with the data - process(bufferData) + // Map the buffer into the memory + val bufferData = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT) + return if (bufferData != null) { + // Do something with the data + process(bufferData) - // Release the buffer - glUnmapBuffer(GL_PIXEL_PACK_BUFFER) - } else throw IllegalStateException("Failed to map the buffer") + // Release the buffer + glUnmapBuffer(GL_PIXEL_PACK_BUFFER) // Unbind the PBO glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) - } - - /** - * 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(query: Int, block: () -> Unit) { - // Start the timer - glBeginQuery(GL_TIME_ELAPSED, query) - - // Perform the transfer - block() - - // Stop the timer - glEndQuery(GL_TIME_ELAPSED) - // Calculate the transfer rate - val time = if (query == 0) uploadTime else downloadTime - if (time > 0) transferRate = (size * 1_000_000_000) / time + // No errors :) + null + } else throw IllegalStateException("Failed to download the buffer") } /** @@ -133,7 +120,6 @@ class PixelBuffer( protected fun finalize() { runGameScheduled { glDeleteBuffers(pboIds) - glDeleteQueries(queries) } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt index a41c27637..bdf0342ab 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt @@ -17,10 +17,8 @@ object TextureRenderer { private val shader = Shader("renderer/pos_tex") private val shaderColored = Shader("renderer/pos_tex_shady") - fun drawTexture(texture: Texture, rect: Rect, block: () -> Unit = {}) { + fun drawTexture(texture: Texture, rect: Rect) { texture.bind() - block() - shader.use() drawInternal(rect) diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt index c14440d29..7b8f0b833 100644 --- a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt +++ b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt @@ -1,12 +1,10 @@ package com.lambda.graphics.video -import com.lambda.graphics.buffer.BufferUsage import com.lambda.graphics.buffer.pbo.PixelBuffer import com.lambda.graphics.texture.Texture import org.bytedeco.ffmpeg.avcodec.AVCodecContext import org.bytedeco.ffmpeg.avutil.AVFrame import org.lwjgl.opengl.GL45C.* -import java.lang.Thread.sleep import java.nio.ByteBuffer class Video( @@ -20,17 +18,16 @@ class Video( private val avFrame: AVFrame? get() = if (iterator.hasNext()) iterator.next() else null private val buffer: ByteBuffer? get() = avFrame?.asByteBuffer() - private val pbo = PixelBuffer(width * height * 4L, buffers = 1, bufferUsage = BufferUsage.DYNAMIC) { + private val pbo = PixelBuffer(width * height * 4L, buffers = 2) { glBindTexture(GL_TEXTURE_2D, id) // Allocate texture storage glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) - glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) } private val startTime = System.nanoTime() @@ -43,6 +40,7 @@ class Video( if (delta > frameTime) { upload(buffer ?: return@use) { + glBindTexture(GL_TEXTURE_2D, id) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) } diff --git a/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt b/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt index f363ab5c0..3bbf659ad 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt @@ -19,9 +19,9 @@ object PBOExample : HudModule( init { onRender { - drawTexture(video, rect) { - video.transfer() - } + video.transfer() + + drawTexture(video, rect) } } } From a8152e919a2fd93ed08d039a03cfc4892c866f47 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:05:46 -0400 Subject: [PATCH 23/45] ref: use 24 bytes for pixel data --- .../main/kotlin/com/lambda/graphics/video/AVUtils.kt | 6 +++--- .../main/kotlin/com/lambda/graphics/video/Video.kt | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt index 62796bf99..d9a53e631 100644 --- a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt @@ -144,7 +144,7 @@ object AVUtils { return null } - val numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB0, codecContext.width(), codecContext.height(), 1) + val numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext.width(), codecContext.height(), 1) val buffer = BytePointer(av_malloc(numBytes.toLong())) val sourcePixFmt = correctForDeprecatedPixelFormat(codecContext.pix_fmt()) @@ -157,7 +157,7 @@ object AVUtils { sourcePixFmt, codecContext.width(), codecContext.height(), - AV_PIX_FMT_RGB0, + AV_PIX_FMT_RGB24, swscale.SWS_BILINEAR, null, null, null as DoublePointer? ) @@ -171,7 +171,7 @@ object AVUtils { pFrameRGB.data(), pFrameRGB.linesize(), buffer, - AV_PIX_FMT_RGB0, + AV_PIX_FMT_RGB24, codecContext.width(), codecContext.height(), 1 diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt index 7b8f0b833..a593461bd 100644 --- a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt +++ b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt @@ -18,11 +18,11 @@ class Video( private val avFrame: AVFrame? get() = if (iterator.hasNext()) iterator.next() else null private val buffer: ByteBuffer? get() = avFrame?.asByteBuffer() - private val pbo = PixelBuffer(width * height * 4L, buffers = 2) { + private val pbo = PixelBuffer(width * height * 3L) { glBindTexture(GL_TEXTURE_2D, id) // Allocate texture storage - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) @@ -40,8 +40,14 @@ class Video( if (delta > frameTime) { upload(buffer ?: return@use) { + // Tell OpenGL that we are using tightly packed data + // If we don't do this, the alignment will truncate + // to 16 bytes because we only have 24 bytes and computers + // don't like this + glPixelStorei(GL_UNPACK_ALIGNMENT, 1) + glBindTexture(GL_TEXTURE_2D, id) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, 0) } lastTime = System.nanoTime() From fac707070fa988d4a3ed222394e731a1cfd7fc68 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:20:00 -0400 Subject: [PATCH 24/45] added renderdoc task --- build.gradle.kts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 24217520f..522d2f4c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,8 @@ +import org.gradle.internal.jvm.* import net.fabricmc.loom.api.LoomGradleExtensionAPI +import org.apache.tools.ant.taskdefs.condition.Os import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.io.FileNotFoundException import java.util.* val modId: String by project @@ -41,6 +44,17 @@ subprojects { if (path == ":common") return@subprojects tasks { + register("renderDoc") { + val javaHome = Jvm.current().javaHome + val gradleWrapper = rootProject.tasks.wrapper.get().jarFile.absolutePath + + commandLine = listOf( + findExecutable("renderdoccmd") + ?: throw FileNotFoundException("Could not find the renderdoccmd executable"), + "capture", /* Remove the following 2 lines if you don't want api validation */ "--opt-api-validation", "--opt-api-validation-unmute", "--opt-hook-children", "--wait-for-exit", "--working-dir", ".", "$javaHome/bin/java", "-Xmx64m", "-Xms64m", /*"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005",*/ "-Dorg.gradle.appname=gradlew", "-Dorg.gradle.java.home=$javaHome", "-classpath", gradleWrapper, "org.gradle.wrapper.GradleWrapperMain", "${this@subprojects.path}:runClient", + ) + } + processResources { // Replaces placeholders in the mod info files filesMatching(targets) { @@ -93,3 +107,10 @@ allprojects { } } } + +private fun findExecutable(executable: String): String? { + val isWindows = Os.isFamily(Os.FAMILY_WINDOWS) + val cmd = if (isWindows) "where" else "which" + + return ProcessBuilder(cmd, executable).start().inputStream.bufferedReader().readText().trim().takeIf { it.isNotBlank() } +} From 9da7aac21501b359f5a9fff921237158c3f1acd4 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:20:27 -0400 Subject: [PATCH 25/45] open bind function --- common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index 0745abb9d..2536c3a95 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -6,5 +6,5 @@ import org.lwjgl.opengl.GL45C.* open class Texture { val id = glGenTextures() - fun bind(slot: Int = 0) = bindTexture(id, slot) + open fun bind(slot: Int = 0) = bindTexture(id, slot) } From d21cc876828fcea0b3c1c52c564767b6df333b49 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:20:49 -0400 Subject: [PATCH 26/45] added error checks and handling --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index ea51bc27d..4561258d4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -1,11 +1,18 @@ package com.lambda.graphics.buffer.pbo +import com.lambda.Lambda.LOG import com.lambda.graphics.buffer.BufferUsage +import com.lambda.graphics.texture.MipmapTexture +import com.lambda.graphics.texture.TextureUtils import com.lambda.threading.runGameScheduled +import com.lambda.util.LambdaResource +import io.netty.buffer.ByteBuf +import org.lwjgl.glfw.GLFW import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL45C.* import org.lwjgl.system.MemoryUtil import java.nio.ByteBuffer +import javax.imageio.ImageIO /** * Represents a Pixel Buffer Object (PBO) that facilitates asynchronous data transfer to the GPU. @@ -57,7 +64,7 @@ class PixelBuffer( * * @param data The [ByteBuffer] containing the pixel data to be uploaded. */ - fun upload(data: ByteBuffer, transfer: () -> Unit = {}) { + fun upload(data: ByteBuffer, transfer: () -> Unit = {}): Throwable? { // Wait for the previous PBO to finish if a fence exists if (fences[index] != 0L) { val ret = glClientWaitSync(fences[index], GL_SYNC_FLUSH_COMMANDS_BIT, 50000000) // 50 ms timeout @@ -70,24 +77,48 @@ class PixelBuffer( // Bind the current PBO for uploading glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) + println("Bind Buffer error ${glGetError()}") + // Map the buffer into the client's memory - val bufferData = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT or GL_MAP_INVALIDATE_BUFFER_BIT) - if (bufferData != null) { + val bufferData = + glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT or GL_MAP_INVALIDATE_BUFFER_BIT) + + println("Map Buffer error ${glGetError()}") + + return if (bufferData != null) { MemoryUtil.memCopy(data, bufferData) // Release the buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) - } else throw IllegalStateException("Failed to map the buffer") + if (!glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) + return IllegalStateException("An unknown error occurred due to GPU memory availability.") + .also { LOG.error(it) } + + println("Unmap error ${glGetError()}") + + // Process + transfer() - // Process - transfer() + println("Transfer error ${glGetError()}") - // Insert a sync object to track when the GPU finishes reading from the PBO - fences[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) + // Insert a sync object to track when the GPU finishes reading from the PBO + fences[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) - // Unbind the PBO - // Once bound with 0, all pixel operations behave normal ways. - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + println("Fence error ${glGetError()}") + + // Unbind the PBO + // Once bound with 0, all pixel operations behave normal ways. + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + + println("Unbind error ${glGetError()}") + + // No errors :) + null + } else IllegalStateException( + """ + Failed to map the buffer\n + There is most likely not enough virtual memory for the program to continue or + """.trimIndent() + ) } /** @@ -111,7 +142,7 @@ class PixelBuffer( // No errors :) null - } else throw IllegalStateException("Failed to download the buffer") + } else return IllegalStateException("Failed to map the buffer") } /** From 4ed152f34e48fe29a764a72b220625fb0ec2e8d6 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 28 Sep 2024 20:14:49 -0400 Subject: [PATCH 27/45] fix: texture buffer binding --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 152 ++++++------------ .../com/lambda/graphics/texture/Texture.kt | 2 +- .../lambda/graphics/texture/TextureUtils.kt | 13 +- .../com/lambda/graphics/video/AVUtils.kt | 23 ++- .../kotlin/com/lambda/graphics/video/Video.kt | 58 ++++--- 5 files changed, 108 insertions(+), 140 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index 4561258d4..bba3bd892 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -1,18 +1,10 @@ package com.lambda.graphics.buffer.pbo -import com.lambda.Lambda.LOG import com.lambda.graphics.buffer.BufferUsage -import com.lambda.graphics.texture.MipmapTexture -import com.lambda.graphics.texture.TextureUtils import com.lambda.threading.runGameScheduled -import com.lambda.util.LambdaResource -import io.netty.buffer.ByteBuf -import org.lwjgl.glfw.GLFW -import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL45C.* -import org.lwjgl.system.MemoryUtil import java.nio.ByteBuffer -import javax.imageio.ImageIO +import kotlin.random.Random /** * Represents a Pixel Buffer Object (PBO) that facilitates asynchronous data transfer to the GPU. @@ -21,11 +13,11 @@ import javax.imageio.ImageIO * - **Process**: * Every function that performs a pixel transfer operation can use buffer objects instead of client memory. * Functions that perform an upload operation, a pixel unpack, will use the buffer object bound to the target GL_PIXEL_UNPACK_BUFFER. - * Functions that perform a download operation, a pixel pack, will use the buffer object bound to the GL_PIXEL_PACK_BUFFER. - * These functions only use buffer objects if one is bound to that particular binding point when the function is called. * If a buffer is bound, then the pointer value that those functions take is not a pointer, but an offset from the beginning of that buffer. * - * @property size The size of the buffer(s). + * @property width The width of the buffer + * @property height The height of the buffer + * @property channels How many channels are present in the data * @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.). * @property init Code to run during the initialisation process. @@ -33,116 +25,76 @@ import javax.imageio.ImageIO * @see Pixel Buffer Object */ class PixelBuffer( - private val size: Long, + private val width: Int, + private val height: Int, + private val channels: Int = 3, private val buffers: Int = 2, private val bufferUsage: BufferUsage = BufferUsage.STATIC, private val init: () -> Unit = {}, ) { + private val size = width * height * channels.toLong() private val pboIds = IntArray(buffers).apply { glGenBuffers(this) } - private val fences = LongArray(buffers) // Synchronization objects private var index = 0 - private val pboSupported = GL.getCapabilities().OpenGL15 - - /** - * Creates a new PBO use context required for everything related in this class. - */ - fun use(block: PixelBuffer.() -> Unit) { - if (buffers != 2) { - index = 0 - } - - // Do the main stuff - block() - - // Swap buffers - index = (index + 1) % buffers - } - /** * 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. + * This method binds the PBO, maps it to client memory, updates the pixel data, and unbinds the PBO. + * If no data is provided, it fills the buffer with random bytes. + * + * @param data The [ByteBuffer] containing the pixel data to be uploaded. If null, the buffer is filled with random data. + * @param transfer A function that takes an integer (the PBO ID) to manage the transfer process. + * @return A [Throwable] if an error occurs during the upload process, or null if the operation is successful. */ - fun upload(data: ByteBuffer, transfer: () -> Unit = {}): Throwable? { - // Wait for the previous PBO to finish if a fence exists - if (fences[index] != 0L) { - val ret = glClientWaitSync(fences[index], GL_SYNC_FLUSH_COMMANDS_BIT, 50000000) // 50 ms timeout - if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) { - glDeleteSync(fences[index]) - fences[index] = 0L - } - } + fun upload( + data: ByteBuffer? = null, + transfer: (Int) -> Unit, + ): Throwable? { + // Copy pixels to whatever this function does + // Use offset instead of pointer + // The buffer MUST be bound, if not, the entire universe breaks apart + // I added this to allow buffer binding at any part of the transfer function + // For example, you might want to bind the buffer after the texture binding + // or other edge cases not handled by the upload function + transfer(pboIds[index]) + + // Swap buffer + index = (index + 1) % buffers - // Bind the current PBO for uploading + // Bind PBO to update pixel source glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) - println("Bind Buffer error ${glGetError()}") - // Map the buffer into the client's memory - val bufferData = - glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT or GL_MAP_INVALIDATE_BUFFER_BIT) - - println("Map Buffer error ${glGetError()}") - - return if (bufferData != null) { - MemoryUtil.memCopy(data, bufferData) + // Note that glMapBuffer() causes sync issue. + // If GPU is working with this buffer, glMapBuffer() will wait(stall) + // for GPU to finish its job. To avoid waiting (stall), 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, size, bufferUsage.gl) + val mappedBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT) + + return if (mappedBuffer != null) { + // Check if we're not mapping out of bounds + if (mappedBuffer.capacity().toLong() != size) + return IllegalStateException("The mapped buffer doesn't match the size! Mapping out of bounds?") + + // Update data directly on the mapped buffer + if (data == null) mappedBuffer.put(Random.nextBytes(width)) // Missingno + else mappedBuffer.put(data) // Release the buffer if (!glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) return IllegalStateException("An unknown error occurred due to GPU memory availability.") - .also { LOG.error(it) } - - println("Unmap error ${glGetError()}") - - // Process - transfer() - - println("Transfer error ${glGetError()}") - - // Insert a sync object to track when the GPU finishes reading from the PBO - fences[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) - - println("Fence error ${glGetError()}") // Unbind the PBO // Once bound with 0, all pixel operations behave normal ways. glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) - println("Unbind error ${glGetError()}") - // No errors :) null - } else IllegalStateException( - """ - Failed to map the buffer\n - There is most likely not enough virtual memory for the program to continue or - """.trimIndent() - ) - } - - /** - * Downloads the data from the PBO from OpenGL back to the client memory - */ - fun download(process: (ByteBuffer) -> Unit): Throwable? { - // Bind the current PBO for writing - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[index]) - - // Map the buffer into the memory - val bufferData = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT) - return if (bufferData != null) { - // Do something with the data - process(bufferData) - - // Release the buffer - glUnmapBuffer(GL_PIXEL_PACK_BUFFER) - - // Unbind the PBO - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) - - // No errors :) - null - } else return IllegalStateException("Failed to map the buffer") + } else IllegalStateException("Failed to map buffer, possibly due to insufficient virtual memory.") } /** @@ -161,11 +113,6 @@ class PixelBuffer( * @throws UnsupportedOperationException If the machine doesn't support PBOs. */ init { - if (buffers < 1) throw IllegalArgumentException("Buffers must be greater than 0") - - if (!pboSupported) - throw UnsupportedOperationException("Client tried to utilize PBOs, but they are not supported on the machine.") - init() // Fill the buffers with null data to allocate the memory spaces @@ -174,6 +121,7 @@ class PixelBuffer( glBufferData(GL_PIXEL_PACK_BUFFER, size, bufferUsage.gl) } - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) // Unbind the buffer + // Unbind the buffer + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) } } diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index 2536c3a95..0745abb9d 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -6,5 +6,5 @@ import org.lwjgl.opengl.GL45C.* open class Texture { val id = glGenTextures() - open fun bind(slot: Int = 0) = bindTexture(id, slot) + fun bind(slot: Int = 0) = bindTexture(id, slot) } diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt index 495d64b9f..1156eb1f8 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt @@ -7,6 +7,7 @@ import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL45C.* import java.awt.* import java.awt.image.BufferedImage +import java.nio.ByteBuffer import kotlin.math.roundToInt import kotlin.math.sqrt @@ -53,7 +54,10 @@ object TextureUtils { glPixelStorei(GL_UNPACK_ALIGNMENT, 4) } - private fun readImage(bufferedImage: BufferedImage): Long { + fun readImage( + bufferedImage: BufferedImage, + format: NativeImage.Format = NativeImage.Format.RGBA, + ): Long { val bytes = encoderPreset .withBufferedImage(bufferedImage) .toBytes() @@ -63,9 +67,14 @@ object TextureUtils { .put(bytes) .flip() - return NativeImage.read(buffer).pointer + return readImage(buffer, format) } + fun readImage( + image: ByteBuffer, + format: NativeImage.Format = NativeImage.Format.RGBA, + ) = NativeImage.read(format, image).pointer + fun getCharImage(font: Font, codePoint: Char): BufferedImage? { if (!font.canDisplay(codePoint)) return null diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt index d9a53e631..a9da8a4f0 100644 --- a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt @@ -1,14 +1,11 @@ package com.lambda.graphics.video import com.lambda.Lambda.LOG -import com.lambda.graphics.gl.Memory.int -import net.minecraft.predicate.entity.DistancePredicate.y import org.bytedeco.ffmpeg.avcodec.AVCodecContext import org.bytedeco.ffmpeg.avformat.AVFormatContext import org.bytedeco.javacpp.* import org.bytedeco.ffmpeg.avcodec.AVPacket -import org.bytedeco.ffmpeg.avutil.AVChannelLayout import org.bytedeco.ffmpeg.avutil.AVFrame import org.bytedeco.ffmpeg.global.avcodec.av_packet_unref import org.bytedeco.ffmpeg.global.avcodec.avcodec_alloc_context3 @@ -20,10 +17,8 @@ import org.bytedeco.ffmpeg.global.avcodec.avcodec_send_packet import org.bytedeco.ffmpeg.global.avformat.* import org.bytedeco.ffmpeg.global.avutil.* import org.bytedeco.ffmpeg.global.swscale -import org.lwjgl.openal.AL10.alBufferData -import sun.security.krb5.Confounder.bytes import java.io.FileOutputStream -import java.io.OutputStream +import java.nio.ByteBuffer /** * Utility object for handling audio-visual (AV) operations using the FFmpeg library. @@ -111,12 +106,11 @@ object AVUtils { * and formats the raw frames into RGB using `sws_scale()`. The frames are yielded using a Kotlin iterator. * * The iterator reads video packets (`av_read_frame()`) and decodes them into frames using `avcodec_send_packet()` - * and `avcodec_receive_frame()`. The iterator limits the frame count to 5 as a demonstration, but this can be - * adapted for full video playback. + * and `avcodec_receive_frame()`. */ fun frameIterator( ctx: Triple - ): Iterator? { + ): Iterator? { var err = -1 val pkt = AVPacket() @@ -174,7 +168,7 @@ object AVUtils { AV_PIX_FMT_RGB24, codecContext.width(), codecContext.height(), - 1 + 1, ) return iterator { @@ -190,18 +184,19 @@ object AVUtils { frame.data(), frame.linesize(), 0, - codecContext.height(), + frame.height(), pFrameRGB.data(), - pFrameRGB.linesize() + pFrameRGB.linesize(), ) - yield(pFrameRGB) + yield(pFrameRGB.data().asByteBuffer()) } av_packet_unref(pkt) } - av_frame_free(frame) // Clean up to prevent memory leak + // Clean up to prevent memory leak + av_frame_free(frame) } } diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt index a593461bd..0d7f2e317 100644 --- a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt +++ b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt @@ -1,54 +1,70 @@ package com.lambda.graphics.video +import com.lambda.Lambda.LOG import com.lambda.graphics.buffer.pbo.PixelBuffer import com.lambda.graphics.texture.Texture import org.bytedeco.ffmpeg.avcodec.AVCodecContext -import org.bytedeco.ffmpeg.avutil.AVFrame import org.lwjgl.opengl.GL45C.* import java.nio.ByteBuffer +import kotlin.random.Random class Video( codecContext: AVCodecContext, - private val iterator: Iterator, + private val iterator: Iterator, ) : Texture() { val width = codecContext.width() val height = codecContext.height() - private val frameTime = 1 / codecContext.framerate().num() + private val frameTime = 1000 / codecContext.framerate().num() - private val avFrame: AVFrame? get() = if (iterator.hasNext()) iterator.next() else null - private val buffer: ByteBuffer? get() = avFrame?.asByteBuffer() + private val buffer: ByteBuffer + get() = if (iterator.hasNext()) iterator.next() else ByteBuffer.allocate(0) - private val pbo = PixelBuffer(width * height * 3L) { + private val pbo = PixelBuffer(width, height, channels = 3) { + // Bind the texture glBindTexture(GL_TEXTURE_2D, id) + // Tell OpenGL that we are using tightly packed data + // If we don't do this, the alignment will truncate + // to 16 bytes because we only have 24 bytes and computers + // don't like this + glPixelStorei(GL_UNPACK_ALIGNMENT, 1) + // Allocate texture storage glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + // Set the texture parameters 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) } - private val startTime = System.nanoTime() - private var lastTime = System.nanoTime() + private var lastTime = System.currentTimeMillis() private var delta = 0L fun transfer() { - pbo.use { - delta = System.nanoTime() - lastTime + with(pbo) { + delta = System.currentTimeMillis() - lastTime if (delta > frameTime) { - upload(buffer ?: return@use) { - // Tell OpenGL that we are using tightly packed data - // If we don't do this, the alignment will truncate - // to 16 bytes because we only have 24 bytes and computers - // don't like this - glPixelStorei(GL_UNPACK_ALIGNMENT, 1) - + upload(buffer) { pbo -> + // Bind the texture and PBO glBindTexture(GL_TEXTURE_2D, id) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, 0) - } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo) + + // Copy pixels from PBO to texture object + // Use offset instead of pointer + glTexSubImage2D( + GL_TEXTURE_2D, // Target + 0, // Mipmap level + 0, 0, // x and y offset + width, height, // width and height of the texture (set to your size) + GL_RGB, // Format (depends on your data) + GL_UNSIGNED_BYTE, // Type (depends on your data) + 0 // PBO offset (for asynchronous transfer) + ) + }?.let(LOG::error) lastTime = System.nanoTime() } From 68fe8e4d06e3ccee7f5a12b55def51bef470c521 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:53:36 -0400 Subject: [PATCH 28/45] refactor: dedicated decoding class --- .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 27 +- .../com/lambda/graphics/video/AVDecoder.kt | 259 ++++++++++++++++++ .../com/lambda/graphics/video/AVUtils.kt | 234 ---------------- .../com/lambda/graphics/video/AudioInfo.kt | 5 + .../lambda/graphics/video/SampleFormats.kt | 28 ++ .../kotlin/com/lambda/graphics/video/Video.kt | 49 ++-- .../com/lambda/graphics/video/VideoInfo.kt | 8 + 7 files changed, 337 insertions(+), 273 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt index bba3bd892..b06800fea 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt @@ -48,15 +48,14 @@ class PixelBuffer( */ fun upload( data: ByteBuffer? = null, - transfer: (Int) -> Unit, + transfer: () -> Unit, ): Throwable? { + // Bind PBO to unpack the data into whatever the transfer function does + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) + // Copy pixels to whatever this function does // Use offset instead of pointer - // The buffer MUST be bound, if not, the entire universe breaks apart - // I added this to allow buffer binding at any part of the transfer function - // For example, you might want to bind the buffer after the texture binding - // or other edge cases not handled by the upload function - transfer(pboIds[index]) + transfer() // Swap buffer index = (index + 1) % buffers @@ -65,14 +64,6 @@ class PixelBuffer( glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) // Map the buffer into the client's memory - // Note that glMapBuffer() causes sync issue. - // If GPU is working with this buffer, glMapBuffer() will wait(stall) - // for GPU to finish its job. To avoid waiting (stall), 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, size, bufferUsage.gl) val mappedBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT) return if (mappedBuffer != null) { @@ -81,7 +72,7 @@ class PixelBuffer( return IllegalStateException("The mapped buffer doesn't match the size! Mapping out of bounds?") // Update data directly on the mapped buffer - if (data == null) mappedBuffer.put(Random.nextBytes(width)) // Missingno + if (data == null || data.limit() == 8) mappedBuffer.put(Random.nextBytes(width)) // Missingno else mappedBuffer.put(data) // Release the buffer @@ -117,11 +108,11 @@ class PixelBuffer( // Fill the buffers with null data to allocate the memory spaces repeat(buffers) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, pboIds[it]) - glBufferData(GL_PIXEL_PACK_BUFFER, size, bufferUsage.gl) + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[it]) + glBufferData(GL_PIXEL_UNPACK_BUFFER, size, bufferUsage.gl) } // Unbind the buffer - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) } } diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt b/common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt new file mode 100644 index 000000000..082e3520e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt @@ -0,0 +1,259 @@ +package com.lambda.graphics.video + +import org.bytedeco.ffmpeg.avcodec.AVCodecContext +import org.bytedeco.ffmpeg.avcodec.AVPacket +import org.bytedeco.ffmpeg.avformat.AVFormatContext +import org.bytedeco.ffmpeg.global.avcodec.* +import org.bytedeco.ffmpeg.global.avformat.* +import org.bytedeco.ffmpeg.global.avutil.* +import org.bytedeco.ffmpeg.global.swscale.* +import org.bytedeco.ffmpeg.swscale.SwsContext +import org.bytedeco.javacpp.BytePointer +import org.bytedeco.javacpp.DoublePointer +import org.bytedeco.javacpp.PointerPointer +import java.nio.ByteBuffer + +class AVDecoder( + path: String, +) { + private val formatContext: AVFormatContext = AVFormatContext(null) + private var videoCodecContext: AVCodecContext? = null + private var audioCodecContext: AVCodecContext? = null + private var videoStreamIndex: Int = -1 + private var audioStreamIndex: Int = -1 + private var swsContext: SwsContext? = null + + private var error = -1 + private val errorBuffer = ByteArray(1024) + + private var videoPTS = 0L + + fun videoInfo(): VideoInfo { + val fps = av_q2d(formatContext.streams(videoStreamIndex).r_frame_rate()) + val timeBase = av_q2d(formatContext.streams(videoStreamIndex).time_base()) + + return VideoInfo( + width = videoCodecContext?.width() ?: 0, + height = videoCodecContext?.height() ?: 0, + frameRate = fps, + frameDuration = { videoPTS * timeBase }, + ) + } + + fun audioInfo(): AudioInfo { + return AudioInfo(0) + } + + fun audioFrameIterator(): Iterator? { + audioCodecContext?.let { codec -> + val audioFrame = av_frame_alloc() + val pkt = AVPacket() + + // Number of bytes per sample or zero if unknown for the given sample format + // https://ffmpeg.org/doxygen/3.3/group__lavu__sampfmts.html#ga0c3c218e1dd570ad4917c69a35a6c77d + val bufferOutputSize = + av_get_bytes_per_sample(codec.sample_fmt()) + + return iterator { + while (av_read_frame(formatContext, pkt) >= 0) { + if (pkt.stream_index() == audioStreamIndex) { + avcodec_send_packet(codec, pkt) + error = avcodec_receive_frame(codec, audioFrame) + } + + if (error >= 0) { + val decodedAudio = + ByteBuffer.allocate(bufferOutputSize * codec.ch_layout().nb_channels()) + + for (ch in 0 until codec.ch_layout().nb_channels()) { + decodedAudio.put( + audioFrame.data(ch).position(bufferOutputSize.toLong()) + .position(bufferOutputSize.toLong()) + .asByteBuffer() + ) + } + + yield(decodedAudio) + } + + // Unreferences the buffer referenced by the packet and + // reset the remaining packet fields to their default + // values + // https://ffmpeg.org/doxygen/trunk/group__lavc__packet.html#ga63d5a489b419bd5d45cfd09091cbcbc2 + av_packet_unref(pkt) + } + + // Clean up to prevent memory leak + av_frame_free(audioFrame) + } + } + + return null + } + + fun videoFrameIterator( + pixelFormat: Int = AV_PIX_FMT_RGB24, + ): Iterator? { + videoCodecContext?.let { codec -> + val pkt = AVPacket() + + val videoFrame = av_frame_alloc() + val pFrameRGB = av_frame_alloc() + + // Returns the size in bytes of the amount of data required + // to store an image + // https://ffmpeg.org/doxygen/trunk/group__lavu__picture.html#ga24a67963c3ae0054a2a4bab35930e694 + val bufferOutputSize = + av_image_get_buffer_size( + pixelFormat, + codec.width(), + codec.height(), + 1, + ).toLong() + + val buffer = BytePointer(av_malloc(bufferOutputSize)) + + // Map deprecated pixel formats to their newest compatible + // versions + val sourcePixFmt = correctForDeprecatedPixelFormat(codec.pix_fmt()) + + // Allocate and return an SwsContext + // https://ffmpeg.org/doxygen/trunk/group__libsws.html#gaf360d1a9e0e60f906f74d7d44f9abfdd + if (swsContext == null) { + swsContext = sws_getContext( + codec.width(), + codec.height(), + sourcePixFmt, + codec.width(), + codec.height(), + pixelFormat, + SWS_BILINEAR, + null, null, null as DoublePointer? + ) + } + + // Setup the data pointers and linesizes based on the image + // https://ffmpeg.org/doxygen/trunk/group__lavu__picture.html#ga5b6ead346a70342ae8a303c16d2b3629 + av_image_fill_arrays( + pFrameRGB.data(), // Destination + pFrameRGB.linesize(), // Destination Linesize + buffer, // Source buffer + pixelFormat, // Pixel Format + codec.width(), // Width + codec.height(), // Height + 1, // Alignment + ) + + return iterator { + while (av_read_frame(formatContext, pkt) >= 0) { + if (pkt.stream_index() == videoStreamIndex) { + avcodec_send_packet(codec, pkt) + error = avcodec_receive_frame(codec, videoFrame) + } + + videoPTS = videoFrame.pts() + + if (error >= 0) { + // Scale the image slice into the destination image + // https://www.ffmpeg.org/doxygen/2.7/group__libsws.html#gae531c9754c9205d90ad6800015046d74 + sws_scale( + swsContext, // Scaling context + videoFrame.data(), // Source slice + videoFrame.linesize(), // Source stride + 0, // Position in the source image of the slice to process + videoFrame.height(), + pFrameRGB.data(), + pFrameRGB.linesize(), // Destination stride + ) + + // We need to set the capacity in order to use + // `asByteBuffer()` + val convertedFrame = + pFrameRGB.data(0).capacity(bufferOutputSize).asByteBuffer() + + yield(convertedFrame) + } + + // Unreferences the buffer referenced by the packet and + // reset the remaining packet fields to their default + // values + // https://ffmpeg.org/doxygen/trunk/group__lavc__packet.html#ga63d5a489b419bd5d45cfd09091cbcbc2 + av_packet_unref(pkt) + } + + // Clean up to prevent memory leak + av_frame_free(videoFrame) + av_frame_free(pFrameRGB) + } + } + + return null + } + + private fun correctForDeprecatedPixelFormat(pixFmt: Int): Int { + // Fix swscaler deprecated pixel format warning + // (YUVJ has been deprecated, change pixel format to regular YUV) + return when (pixFmt) { + AV_PIX_FMT_YUVJ420P -> AV_PIX_FMT_YUV420P + AV_PIX_FMT_YUVJ422P -> AV_PIX_FMT_YUV422P + AV_PIX_FMT_YUVJ444P -> AV_PIX_FMT_YUV444P + AV_PIX_FMT_YUVJ440P -> AV_PIX_FMT_YUV440P + else -> pixFmt + } + } + + fun close() { + videoCodecContext?.close() + audioCodecContext?.close() + formatContext.close() + } + + init { + error = avformat_open_input(formatContext, path, null, null) + if (error < 0) { + av_strerror(error, errorBuffer, 1024) + throw IllegalStateException("Failed to open video file at $path: ${errorBuffer.decodeToString()}") + } + + error = avformat_find_stream_info(formatContext, null as PointerPointer<*>?) + if (error < 0) { + av_strerror(error, errorBuffer, 1024) + throw IllegalStateException("Failed to find stream info for video file at $path: ${errorBuffer.decodeToString()}") + } + + av_dump_format(formatContext, 0, path, 0) + + // Setup audio and video codecs + for (i in 0 until formatContext.nb_streams()) { + val stream = formatContext.streams(i) + + when (stream.codecpar().codec_type()) { + AVMEDIA_TYPE_VIDEO -> { + if (videoStreamIndex >= 0) continue + + // Allocate video codec context and set codec parameters + videoCodecContext = avcodec_alloc_context3(null) + avcodec_parameters_to_context(videoCodecContext, stream.codecpar()) + + val codec = avcodec_find_decoder(videoCodecContext?.codec_id() ?: 0) + avcodec_open2(videoCodecContext, codec, null as PointerPointer<*>?) + + videoStreamIndex = i + } + + AVMEDIA_TYPE_AUDIO -> { + if (audioStreamIndex >= 0) continue + + // Allocate audio codec context and set codec parameters + audioCodecContext = avcodec_alloc_context3(null) + avcodec_parameters_to_context(audioCodecContext, stream.codecpar()) + + val codec = avcodec_find_decoder(audioCodecContext?.codec_id() ?: 0) + avcodec_open2(audioCodecContext, codec, null as PointerPointer<*>?) + + audioStreamIndex = i + } + } + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt b/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt deleted file mode 100644 index a9da8a4f0..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/AVUtils.kt +++ /dev/null @@ -1,234 +0,0 @@ -package com.lambda.graphics.video - -import com.lambda.Lambda.LOG -import org.bytedeco.ffmpeg.avcodec.AVCodecContext -import org.bytedeco.ffmpeg.avformat.AVFormatContext - -import org.bytedeco.javacpp.* -import org.bytedeco.ffmpeg.avcodec.AVPacket -import org.bytedeco.ffmpeg.avutil.AVFrame -import org.bytedeco.ffmpeg.global.avcodec.av_packet_unref -import org.bytedeco.ffmpeg.global.avcodec.avcodec_alloc_context3 -import org.bytedeco.ffmpeg.global.avcodec.avcodec_find_decoder -import org.bytedeco.ffmpeg.global.avcodec.avcodec_open2 -import org.bytedeco.ffmpeg.global.avcodec.avcodec_parameters_to_context -import org.bytedeco.ffmpeg.global.avcodec.avcodec_receive_frame -import org.bytedeco.ffmpeg.global.avcodec.avcodec_send_packet -import org.bytedeco.ffmpeg.global.avformat.* -import org.bytedeco.ffmpeg.global.avutil.* -import org.bytedeco.ffmpeg.global.swscale -import java.io.FileOutputStream -import java.nio.ByteBuffer - -/** - * Utility object for handling audio-visual (AV) operations using the FFmpeg library. - * Provides functions to create codec contexts, format contexts, and iterating through video frames. - * - * ## Resources for further understanding: - * - [FFmpeg Documentation](https://ffmpeg.org/documentation.html) - * - [JAVACV Documentation](https://bytedeco.org/) - * - [Understanding Video Decoding in FFmpeg](https://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html) - * - [FFmpeg's AVFormatContext and AVCodecContext](https://ffmpeg.org/doxygen/trunk/structAVFormatContext.html) - * - [Swscale Library for Image Scaling](https://ffmpeg.org/doxygen/trunk/group__libsws.html) - */ -object AVUtils { - - /** - * Creates an `AVCodecContext` and an `AVFormatContext` for the video file at the specified path. - * - * @param path The file path to the video file. - * @return A Triple containing an `AVCodecContext`, an `AVFormatContext` and the stream codec index, or `null` if the context creation fails. - * - * ### Detailed Explanation: - * This function opens the video file using FFmpeg's `avformat_open_input()` function to initialize the `AVFormatContext`. - * After opening the file, `avformat_find_stream_info()` extracts information about the streams (audio, video, etc.). - * The function searches for a video stream (type `AVMEDIA_TYPE_VIDEO`), and if found, initializes a codec context - * with `avcodec_alloc_context3()`. The stream's codec parameters are then copied to the codec context using - * `avcodec_parameters_to_context()`. - * If any errors occur during these steps, the error message is logged, and the function returns `null`. - */ - fun createContext( - path: String - ): Triple< - AVCodecContext, - AVFormatContext, - Int>? { - val errStr = ByteArray(1024) - var err = -1 - var streamIndex = -1 - val formatContext = AVFormatContext(null) - - err = avformat_open_input(formatContext, path, null, null) - if (err < 0) { - av_strerror(err, errStr, 1024) - LOG.error("Failed to open video file at $path: ${errStr.decodeToString()}") - return null - } - - err = avformat_find_stream_info(formatContext, null as PointerPointer<*>?) - if (err < 0) { - av_strerror(err, errStr, 1024) - LOG.error("Failed to find stream info for video file at $path: ${errStr.decodeToString()}") - return null - } - - av_dump_format(formatContext, 0, path, 0) - - for (i in 0 until formatContext.nb_streams()) { - if (formatContext.streams(i).codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) { - streamIndex = i - break - } - } - - if (streamIndex == -1) { - LOG.error("Video stream not found in file at $path") - return null - } - - // Allocate codec context and set codec parameters - val codecContext = avcodec_alloc_context3(null) - avcodec_parameters_to_context(codecContext, formatContext.streams(streamIndex).codecpar()) - - return Triple(codecContext, formatContext, streamIndex) - } - - /** - * Returns an iterator over the decoded video frames for the given codec and format context pair. - * - * @param ctx A Pair of `AVCodecContext` and `AVFormatContext` created from `createContext()`. - * @return An `Iterator` of `AVFrame` representing the video frames, or `null` if initialization fails. - * - * ### Detailed Explanation: - * This function sets up a decoding pipeline using FFmpeg's `avcodec_find_decoder()` and `avcodec_open2()` - * to find and initialize the video codec. It also allocates memory for `AVFrame` structures used for decoding. - * To convert the decoded frames into RGB format, the Swscale library is used (`sws_getContext()`), which scales - * and formats the raw frames into RGB using `sws_scale()`. The frames are yielded using a Kotlin iterator. - * - * The iterator reads video packets (`av_read_frame()`) and decodes them into frames using `avcodec_send_packet()` - * and `avcodec_receive_frame()`. - */ - fun frameIterator( - ctx: Triple - ): Iterator? { - var err = -1 - val pkt = AVPacket() - - val codecContext = ctx.first - val formatContext = ctx.second - val streamIndex = ctx.third - - // Find and initialize the decoder for the video stream - val codec = avcodec_find_decoder(codecContext.codec_id()) - if (codec == null) { - LOG.error("Unsupported codec for video file") - return null - } - - err = avcodec_open2(codecContext, codec, null as PointerPointer<*>?) - if (err < 0) { - LOG.error("Failed to open codec for video decoding") - return null - } - - val frame = av_frame_alloc() ?: return null - - val pFrameRGB = av_frame_alloc() ?: run { - LOG.error("Failed to allocate AV frame for RGB conversion") - return null - } - - val numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext.width(), codecContext.height(), 1) - val buffer = BytePointer(av_malloc(numBytes.toLong())) - - val sourcePixFmt = correctForDeprecatedPixelFormat(codecContext.pix_fmt()) - - // Initialize Swscale context for converting YUV to RGB - // This is required to pass the image data to OpenGL. - val swsCtx = swscale.sws_getContext( - codecContext.width(), - codecContext.height(), - sourcePixFmt, - codecContext.width(), - codecContext.height(), - AV_PIX_FMT_RGB24, - swscale.SWS_BILINEAR, - null, null, null as DoublePointer? - ) - - if (swsCtx == null) { - LOG.error("Failed to initialize Swscale context") - return null - } - - av_image_fill_arrays( - pFrameRGB.data(), - pFrameRGB.linesize(), - buffer, - AV_PIX_FMT_RGB24, - codecContext.width(), - codecContext.height(), - 1, - ) - - return iterator { - while (av_read_frame(formatContext, pkt) >= 0) { - if (pkt.stream_index() == streamIndex) { - avcodec_send_packet(codecContext, pkt) - err = avcodec_receive_frame(codecContext, frame) - } - - if (err >= 0) { - swscale.sws_scale( - swsCtx, - frame.data(), - frame.linesize(), - 0, - frame.height(), - pFrameRGB.data(), - pFrameRGB.linesize(), - ) - - yield(pFrameRGB.data().asByteBuffer()) - } - - av_packet_unref(pkt) - } - - // Clean up to prevent memory leak - av_frame_free(frame) - } - } - - private fun correctForDeprecatedPixelFormat(pixFmt: Int): Int { - // Fix swscaler deprecated pixel format warning - // (YUVJ has been deprecated, change pixel format to regular YUV) - return when (pixFmt) { - AV_PIX_FMT_YUVJ420P -> AV_PIX_FMT_YUV420P - AV_PIX_FMT_YUVJ422P -> AV_PIX_FMT_YUV422P - AV_PIX_FMT_YUVJ444P -> AV_PIX_FMT_YUV444P - AV_PIX_FMT_YUVJ440P -> AV_PIX_FMT_YUV440P - else -> pixFmt - } - } - - fun saveFrame(pFrame: AVFrame, width: Int, height: Int, dest: String) { - // Open file - val pFile = FileOutputStream("$dest/output.ppm") - - // Write header - pFile.write("P6\n$width $height\n255\n".toByteArray()) - - // Write pixel data - val data = pFrame.data(0) - val bytes = ByteArray(width * 3) - val lineSize = pFrame.linesize(0) - for (y in 0L until height) { - data.position(y * lineSize).get(bytes) - pFile.write(bytes) - } - - // Close file - pFile.close() - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt b/common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt new file mode 100644 index 000000000..a57945d63 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt @@ -0,0 +1,5 @@ +package com.lambda.graphics.video + +data class AudioInfo( + val a: Int, +) diff --git a/common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt b/common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt new file mode 100644 index 000000000..1af73101a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt @@ -0,0 +1,28 @@ +package com.lambda.graphics.video + +import org.bytedeco.ffmpeg.global.avutil.* +import java.nio.ByteOrder + +enum class SampleFormats( + val sample: Int, + val formatBe: String, + val formatLe: String, +) { + U8(AV_SAMPLE_FMT_U8, "u8", "u8"), + S16(AV_SAMPLE_FMT_S16, "s16be", "s16le"), + S32(AV_SAMPLE_FMT_S32, "s32be", "s32le"), + F32(AV_SAMPLE_FMT_FLT, "f32be", "f32le"), + F64(AV_SAMPLE_FMT_DBL, "f64be", "f64le"); + + fun AV_NE(): String { + return if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) formatLe + else formatBe + } + + companion object { + fun getSampleFromFormat(format: Int): SampleFormats { + return SampleFormats.entries.find { it.sample == format } + ?: throw IllegalArgumentException("Unknown format: $format") + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt index 0d7f2e317..6f7d4f238 100644 --- a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt +++ b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt @@ -4,20 +4,24 @@ import com.lambda.Lambda.LOG import com.lambda.graphics.buffer.pbo.PixelBuffer import com.lambda.graphics.texture.Texture import org.bytedeco.ffmpeg.avcodec.AVCodecContext +import org.bytedeco.ffmpeg.avformat.AVFormatContext import org.lwjgl.opengl.GL45C.* import java.nio.ByteBuffer -import kotlin.random.Random class Video( - codecContext: AVCodecContext, - private val iterator: Iterator, + private val videoInfo: VideoInfo, + private val audioInfo: AudioInfo, + private val videoIterator: Iterator?, + private val audioIterator: Iterator?, ) : Texture() { - val width = codecContext.width() - val height = codecContext.height() - private val frameTime = 1000 / codecContext.framerate().num() + val width = videoInfo.width + val height = videoInfo.height - private val buffer: ByteBuffer - get() = if (iterator.hasNext()) iterator.next() else ByteBuffer.allocate(0) + private val videoBuffer: ByteBuffer + get() = if (videoIterator?.hasNext() == true) videoIterator.next() else ByteBuffer.allocate(0) + + private val audioBuffer: ByteBuffer + get() = if (audioIterator?.hasNext() == true) audioIterator.next() else ByteBuffer.allocate(0) private val pbo = PixelBuffer(width, height, channels = 3) { // Bind the texture @@ -45,13 +49,12 @@ class Video( fun transfer() { with(pbo) { - delta = System.currentTimeMillis() - lastTime + delta = System.currentTimeMillis() - lastTime / 1000 - if (delta > frameTime) { - upload(buffer) { pbo -> + if (delta >= videoInfo.frameDuration()) { + upload(videoBuffer) { // Bind the texture and PBO glBindTexture(GL_TEXTURE_2D, id) - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo) // Copy pixels from PBO to texture object // Use offset instead of pointer @@ -64,27 +67,31 @@ class Video( GL_UNSIGNED_BYTE, // Type (depends on your data) 0 // PBO offset (for asynchronous transfer) ) + + // Unbind the texture + glBindTexture(GL_TEXTURE_2D, 0) }?.let(LOG::error) - lastTime = System.nanoTime() + lastTime = System.currentTimeMillis() } } } companion object { /** - * Retrieves a video from the resources folder. + * Retrieves a video from the resources' folder. * * @param path The path to the image. */ fun fromResource(path: String): Video { - val ctx = AVUtils.createContext(path) ?: - throw IllegalStateException("Could not create context from path: $path") - - val iterator = AVUtils.frameIterator(ctx) ?: - throw IllegalStateException("Could not create frame iterator: $path") - - return Video(ctx.first, iterator) + val decoder = AVDecoder(path) + + return Video( + decoder.videoInfo(), + decoder.audioInfo(), + decoder.videoFrameIterator(), + decoder.audioFrameIterator(), + ) } } } diff --git a/common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt b/common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt new file mode 100644 index 000000000..1e5395b8e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt @@ -0,0 +1,8 @@ +package com.lambda.graphics.video + +data class VideoInfo( + val width: Int, + val height: Int, + val frameRate: Double, + val frameDuration: () -> Double, +) From a3e08029a665d6bdaa7ddd6d4c6d5d4947d71ab7 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:31:39 -0400 Subject: [PATCH 29/45] refactor: vertex pipeline --- .../com/lambda/graphics/buffer/BufferUsage.kt | 12 - .../com/lambda/graphics/buffer/FrameBuffer.kt | 7 +- .../com/lambda/graphics/buffer/IBuffer.kt | 195 +++++++++++++ .../buffer/{vao => }/IRenderContext.kt | 4 +- .../lambda/graphics/buffer/VertexPipeline.kt | 199 ++++++++++++++ .../lambda/graphics/buffer/pbo/PixelBuffer.kt | 118 -------- .../graphics/buffer/pixel/PixelBuffer.kt | 139 ++++++++++ .../com/lambda/graphics/buffer/vao/VAO.kt | 227 --------------- .../graphics/buffer/vertex/ElementBuffer.kt | 40 +++ .../graphics/buffer/vertex/VertexArray.kt | 30 ++ .../graphics/buffer/vertex/VertexBuffer.kt | 45 +++ .../attributes}/VertexAttrib.kt | 4 +- .../attributes}/VertexMode.kt | 4 +- .../kotlin/com/lambda/graphics/gl/Memory.kt | 22 +- .../kotlin/com/lambda/graphics/gl/VaoUtils.kt | 37 --- .../graphics/renderer/esp/ChunkedESP.kt | 19 +- .../esp/builders/StaticESPBuilders.kt | 1 - .../graphics/renderer/esp/global/StaticESP.kt | 3 +- .../renderer/esp/impl/DynamicESPRenderer.kt | 4 +- .../graphics/renderer/esp/impl/ESPRenderer.kt | 20 +- .../renderer/esp/impl/StaticESPRenderer.kt | 8 +- .../graphics/renderer/gui/TextureRenderer.kt | 9 +- .../renderer/gui/font/FontRenderer.kt | 8 +- .../renderer/gui/rect/AbstractRectRenderer.kt | 10 +- .../renderer/gui/rect/FilledRectRenderer.kt | 4 +- .../renderer/gui/rect/OutlineRectRenderer.kt | 4 +- .../com/lambda/graphics/video/AVDecoder.kt | 259 ------------------ .../com/lambda/graphics/video/AudioInfo.kt | 5 - .../lambda/graphics/video/SampleFormats.kt | 28 -- .../kotlin/com/lambda/graphics/video/Video.kt | 97 ------- .../com/lambda/graphics/video/VideoInfo.kt | 8 - .../com/lambda/module/hud/PBOExample.kt | 27 -- .../lambda/module/modules/render/Particles.kt | 8 +- 33 files changed, 719 insertions(+), 886 deletions(-) delete mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt rename common/src/main/kotlin/com/lambda/graphics/buffer/{vao => }/IRenderContext.kt (94%) create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt rename common/src/main/kotlin/com/lambda/graphics/buffer/{vao/vertex => vertex/attributes}/VertexAttrib.kt (95%) rename common/src/main/kotlin/com/lambda/graphics/buffer/{vao/vertex => vertex/attributes}/VertexMode.kt (82%) delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/Video.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt delete mode 100644 common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt deleted file mode 100644 index 7300d8e3f..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt +++ /dev/null @@ -1,12 +0,0 @@ -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), - STREAM(GL_STREAM_DRAW); -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt index db164955a..60add450f 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt @@ -2,9 +2,8 @@ package com.lambda.graphics.buffer import com.lambda.Lambda.mc import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.GlStateUtils.withBlendFunc import com.lambda.graphics.shader.Shader import com.lambda.graphics.texture.TextureUtils.bindTexture @@ -122,7 +121,7 @@ class FrameBuffer(private val depth: Boolean = false) { } companion object { - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) + private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private var lastFrameBuffer: Int? = null } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt new file mode 100644 index 000000000..2d2b69b7e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -0,0 +1,195 @@ +package com.lambda.graphics.buffer + +import org.lwjgl.opengl.GL30C.* +import java.nio.ByteBuffer + +interface IBuffer { + /** + * Specifies how many buffer must be used + * + * | Number of Buffers | Purpose | + * |-------------------|---------------------------------------------------------------------------------------------------------------| + * | 1 Buffer | Simple operations like storing vertex data or a single texture. | + * | 2 Buffers | Double buffering for smooth rendering (alternating between writing to one buffer while reading from another). | + * | 3 Buffers | Triple buffering for improved frame rate and reduced screen tearing at the cost of memory usage. | + * + * In double buffering, you have a front buffer (A) and a back buffer (B). While one is displayed (A), the other (B) is being drawn to. After drawing, the buffers are swapped. However, during the swap (often synchronized with vertical retrace to avoid tearing), drawing cannot resume until it completes, potentially causing delays. + * + * In triple buffering, there is a front buffer (A) and two back buffers (B and C). This allows drawing to continue on the second back buffer (C) while waiting for the swap to finish between the front (A) and the first back buffer (B). This reduces idle time and improves frame rates, especially if the app runs slower than the monitor's refresh rate. + * + * Triple buffering helps maintain smoother frame rates, but if your app runs faster than the monitor's refresh rate, it offers little benefit as you eventually still wait for vblank synchronization. + */ + val buffers: Int + + /** + * Specifies how the buffers are used + * + * | Buffer Usage | Description | + * |--------------------------------|-----------------------------------------------------------------| + * | GL_STREAM_DRAW | Data is set once and used a few times for drawing. | + * | GL_STREAM_READ | Data is set once and used a few times for reading. | + * | GL_STREAM_COPY | Data is set once and used a few times for copying. | + * | GL_STATIC_DRAW | Data is set once and used many times for drawing. | + * | GL_STATIC_READ | Data is set once and used many times for reading. | + * | GL_STATIC_COPY | Data is set once and used many times for copying. | + * | GL_DYNAMIC_DRAW | Data is modified repeatedly and used many times for drawing. | + * | GL_DYNAMIC_READ | Data is modified repeatedly and used many times for reading. | + * | GL_DYNAMIC_COPY | Data is modified repeatedly and used many times for copying. | + */ + val usage: Int + + /** + * Specifies the target to which the buffer object is bound which must be one + * of the following: + * + * | Buffer Binding Target | Purpose | + * |-------------------------------|--------------------------------------| + * | GL_ARRAY_BUFFER | Vertex attributes | + * | GL_ATOMIC_COUNTER_BUFFER | Atomic counter storage | + * | GL_COPY_READ_BUFFER | Buffer copy source | + * | GL_COPY_WRITE_BUFFER | Buffer copy destination | + * | GL_DISPATCH_INDIRECT_BUFFER | Indirect compute dispatch commands | + * | GL_DRAW_INDIRECT_BUFFER | Indirect command arguments | + * | GL_ELEMENT_ARRAY_BUFFER | Vertex array indices | + * | GL_PIXEL_PACK_BUFFER | Pixel read target | + * | GL_PIXEL_UNPACK_BUFFER | Texture data source | + * | GL_QUERY_BUFFER | Query result buffer | + * | GL_SHADER_STORAGE_BUFFER | Read-write storage for shaders | + * | GL_TEXTURE_BUFFER | Texture data buffer | + * | GL_TRANSFORM_FEEDBACK_BUFFER | Transform feedback buffer | + * | GL_UNIFORM_BUFFER | Uniform block storage | + */ + val target: Int + + /** + * Specifies a combination of access flags indicating the desired + * access to the mapping range and must contain one or more of the following: + * + * | Flag | Description | Disclaimer | + * |-------------------------------|-----------------------------------------------------|---------------------------------------------------------------| + * | GL_MAP_READ_BIT | Allows reading buffer data. | Undefined if used without this flag. | + * | GL_MAP_WRITE_BIT | Allows modifying buffer data. | Undefined if used without this flag. | + * | GL_MAP_PERSISTENT_BIT | Enables persistent mapping during GL operations. | Requires proper allocation with GL_MAP_PERSISTENT_BIT. | + * | GL_MAP_COHERENT_BIT | Ensures changes are visible without extra steps. | Without this, explicit sync is needed. | + * | GL_MAP_INVALIDATE_RANGE_BIT | Discards previous contents of the mapped range. | Cannot be used with GL_MAP_READ_BIT. | + * | GL_MAP_INVALIDATE_BUFFER_BIT | Discards previous contents of the entire buffer. | Cannot be used with GL_MAP_READ_BIT. | + * | GL_MAP_FLUSH_EXPLICIT_BIT | Requires explicit flushing of modified sub-ranges. | Only with GL_MAP_WRITE_BIT. Data may be undefined if skipped. | + * | GL_MAP_UNSYNCHRONIZED_BIT | Skips synchronization before mapping. | May cause data corruption if regions overlap. | + */ + val access: Int + + /** + * Index of the current buffer + */ + var index: Int + + /** + * List of all the buffers + */ + val bufferIds: IntArray + + /** + * Binds the buffer id to the [target] + */ + fun bind(id: Int) = glBindBuffer(target, id) + + /** + * Binds current the buffer [index] to the [target] + */ + fun bind() = bind(bufferIds[index]) + + /** + * Swaps the buffer [index] if [buffers] is greater than 1 + */ + fun swap() { index = (index + 1) % buffers } + + /** + * Grows the backing buffers + * This function should not be called frequently + * + * @param size The size of the new buffer + */ + fun grow(size: Long) { + bufferIds.forEach { bufferId -> + // Orphan the buffer and allocate a new one + glBindBuffer(target, bufferId) + glBufferData(target, size, usage) + } + } + + /** + * Maps all or part of a buffer object's data store into the client's address space + * + * @param offset Specifies the starting offset within the buffer of the range to be mapped. + * @param size Specifies the length of the range to be mapped. + * @param block Lambda scope with the mapped buffer passed in + * @return Error encountered during the mapping process + */ + fun map( + offset: Long, + size: Long, + block: (ByteBuffer) -> Unit + ): Throwable? { + if( + target <= 0 + ) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if( + offset < 0 || + size < 0 + ) return IllegalArgumentException("Invalid offset or size parameter offset: $offset size: $size") + + if( + offset + size > glGetBufferParameteri(target, GL_BUFFER_SIZE) + ) return IllegalArgumentException("Out of bound mapping: $offset + $size > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") + + if( + glGetBufferParameteri(target, GL_BUFFER_SIZE) == GL_TRUE + ) return IllegalStateException("Buffer is already mapped, make sure to follow the documentation") + + if( + access and GL_MAP_WRITE_BIT == 0 && + access and GL_MAP_READ_BIT == 0 + ) return IllegalArgumentException("Neither GL_MAP_READ_BIT nor GL_MAP_WRITE_BIT is set") + + if( + access and GL_MAP_READ_BIT != 0 && + (access and GL_MAP_INVALIDATE_RANGE_BIT == 0 || + access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || + access and GL_MAP_UNSYNCHRONIZED_BIT == 0 + ) + ) return IllegalArgumentException("GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set.") + + // Map the buffer into the client's memory + val sharedRegion = glMapBufferRange(target, offset, size.toLong(), access) + ?: return IllegalStateException("Failed to map buffer, possibly due to insufficient virtual memory.") + + // Update data on the shared buffer + block(sharedRegion) + + // Release the buffer + if (!glUnmapBuffer(target)) + return IllegalStateException("An unknown error occurred due to GPU memory availability.") + + return null + } + + /** + * Sets the given data into the client mapped memory and executes the provided processing function to manage data transfer. + * + * @param data Data to set in memory + * @param offset The starting offset within the buffer of the range to be mapped + * @return Error encountered during the mapping process + */ + fun upload(data: ByteArray, offset: Long): Throwable? = + upload(ByteBuffer.wrap(data), offset) + + /** + * Sets the given data into the client mapped memory and executes the provided processing function to manage data transfer. + * + * @param data Data to set in memory + * @param offset The starting offset within the buffer of the range to be mapped + * @return Error encountered during the mapping process + */ + fun upload(data: ByteBuffer, offset: Long): Throwable? +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/IRenderContext.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt similarity index 94% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/IRenderContext.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt index df8765038..d766d6caf 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/IRenderContext.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt @@ -1,4 +1,4 @@ -package com.lambda.graphics.buffer.vao +package com.lambda.graphics.buffer import java.awt.Color @@ -26,4 +26,4 @@ interface IRenderContext { fun use(block: IRenderContext.() -> Unit) { block() } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt new file mode 100644 index 000000000..95e60871e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -0,0 +1,199 @@ +package com.lambda.graphics.buffer + +import com.lambda.Lambda.LOG +import com.lambda.graphics.buffer.vertex.ElementBuffer +import com.lambda.graphics.buffer.vertex.VertexArray +import com.lambda.graphics.buffer.vertex.VertexBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.Matrices +import com.lambda.graphics.gl.Memory +import com.lambda.graphics.gl.Memory.address +import com.lambda.graphics.gl.Memory.byteBuffer +import com.lambda.graphics.gl.Memory.capacity +import com.lambda.graphics.gl.Memory.copy +import com.lambda.graphics.gl.Memory.int +import org.joml.Vector4d +import org.lwjgl.opengl.GL20C.* +import java.awt.Color + +class VertexPipeline( + private val mode: VertexMode, + attributes: VertexAttrib.Group, +) : IRenderContext { + private val stride = attributes.stride + private val size = stride * mode.indicesCount + + private val vao = VertexArray() + private val vbo = VertexBuffer(mode, attributes) + private val ebo = ElementBuffer(mode) + + private var vertices = byteBuffer(size * 256 * 4) + private var verticesPointer = address(vertices) + private var verticesPosition = verticesPointer + + private var indices = byteBuffer(mode.indicesCount * 512 * 4) + private var indicesPointer = address(indices) + private var indicesCount = 0 + private var uploadedIndices = 0 + + private var vertexIndex = 0 + + override fun vec3(x: Double, y: Double, z: Double): VertexPipeline { + verticesPosition += Memory.vec3(verticesPosition, x, y, z) + return this + } + + override fun vec2(x: Double, y: Double): VertexPipeline { + verticesPosition += Memory.vec2(verticesPosition, x, y) + return this + } + + override fun vec3m(x: Double, y: Double, z: Double): IRenderContext { + Matrices.vertexTransformer?.let { mat -> + val vec = Vector4d(x, y, z, 1.0).apply(mat::transform) + vec3(vec.x, vec.y, vec.z) + } ?: vec3(x, y, z) + + return this + } + + override fun vec2m(x: Double, y: Double): IRenderContext { + Matrices.vertexTransformer?.let { mat -> + val vec = Vector4d(x, y, 0.0, 1.0).apply(mat::transform) + vec2(vec.x, vec.y) + } ?: vec2(x, y) + return this + } + + override fun float(v: Double): VertexPipeline { + verticesPosition += Memory.float(verticesPosition, v) + return this + } + + override fun color(color: Color): VertexPipeline { + verticesPosition += Memory.color(verticesPosition, color) + return this + } + + override fun end() = vertexIndex++ + + override fun putLine(vertex1: Int, vertex2: Int) { + growIndices(2) + val p = indicesPointer + indicesCount * 4L + + int(p + 0, vertex1) + int(p + 4, vertex2) + indicesCount += 2 + } + + override fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) { + growIndices(3) + val p = indicesPointer + indicesCount * 4L + + int(p + 0, vertex1) + int(p + 4, vertex2) + int(p + 8, vertex3) + indicesCount += 3 + } + + override fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) { + growIndices(6) + val p = indicesPointer + indicesCount * 4L + + int(p + 0, vertex1) + int(p + 4, vertex2) + int(p + 8, vertex3) + int(p + 12, vertex3) + int(p + 16, vertex4) + int(p + 20, vertex1) + indicesCount += 6 + } + + override fun grow(amount: Int) { + val cap = vertices.capacity + if ((vertexIndex + amount + 1) * size < cap) return + + val offset = verticesPosition - verticesPointer + var newSize = cap * 2 + if (newSize % size != 0) newSize += newSize % size + val newVertices = byteBuffer(newSize) + + val from = address(vertices) + val to = address(newVertices) + copy(from, to, offset) + + vbo.grow(newSize.toLong()) + + vertices = newVertices + verticesPointer = address(vertices) + verticesPosition = verticesPointer + offset + } + + private fun growIndices(amount: Int) { + val cap = indices.capacity + if ((indicesCount + amount) * 4 < cap) return + + var newSize = cap * 2 + if (newSize % mode.indicesCount != 0) newSize += newSize % (mode.indicesCount * 4) + val newIndices = byteBuffer(newSize) + + val from = address(indices) + val to = address(newIndices) + copy(from, to, indicesCount * 4L) + + ebo.grow(newSize.toLong()) + + indices = newIndices + indicesPointer = address(indices) + } + + override fun render() { + if (uploadedIndices <= 0) return + + vao.bind() + glDrawElements(mode.gl, uploadedIndices, GL_UNSIGNED_INT, 0) + vao.bind(0) + } + + override fun upload() { + if (indicesCount <= 0) return + + val vboData = vertices.limit((verticesPosition - verticesPointer).toInt()) + val eboData = indices.limit(indicesCount * 4) + + vbo.upload(vboData, 0)?.let(LOG::error) + ebo.upload(eboData, 0)?.let(LOG::error) + + uploadedIndices = indicesCount + } + + override fun clear() { + verticesPosition = verticesPointer + vertexIndex = 0 + indicesCount = 0 + uploadedIndices = 0 + } + + init { + // All the buffers have been generated, all we have to + // do now it bind them correctly and populate them + vao.bind() + vbo.bind() + ebo.bind() + + // Populate the buffers + attributes.attributes + .foldIndexed(0L) { index, pointer, attrib -> + glEnableVertexAttribArray(index) + glVertexAttribPointer(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) + + attrib.size.toLong() + } + + // Unbind everything to avoid accidental modification + vao.bind(0) + vbo.bind(0) + ebo.bind(0) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt deleted file mode 100644 index b06800fea..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.lambda.graphics.buffer.pbo - -import com.lambda.graphics.buffer.BufferUsage -import com.lambda.threading.runGameScheduled -import org.lwjgl.opengl.GL45C.* -import java.nio.ByteBuffer -import kotlin.random.Random - -/** - * 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 upload and download data efficiently. - * - * - **Process**: - * Every function that performs a pixel transfer operation can use buffer objects instead of client memory. - * Functions that perform an upload operation, a pixel unpack, will use the buffer object bound to the target GL_PIXEL_UNPACK_BUFFER. - * If a buffer is bound, then the pointer value that those functions take is not a pointer, but an offset from the beginning of that buffer. - * - * @property width The width of the buffer - * @property height The height of the buffer - * @property channels How many channels are present in the data - * @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.). - * @property init Code to run during the initialisation process. - * - * @see Pixel Buffer Object - */ -class PixelBuffer( - private val width: Int, - private val height: Int, - private val channels: Int = 3, - private val buffers: Int = 2, - private val bufferUsage: BufferUsage = BufferUsage.STATIC, - private val init: () -> Unit = {}, -) { - private val size = width * height * channels.toLong() - private val pboIds = IntArray(buffers).apply { glGenBuffers(this) } - private var index = 0 - - /** - * Uploads the given pixel data to the PBO and executes the provided processing function to manage the PBO's data transfer. - * - * This method binds the PBO, maps it to client memory, updates the pixel data, and unbinds the PBO. - * If no data is provided, it fills the buffer with random bytes. - * - * @param data The [ByteBuffer] containing the pixel data to be uploaded. If null, the buffer is filled with random data. - * @param transfer A function that takes an integer (the PBO ID) to manage the transfer process. - * @return A [Throwable] if an error occurs during the upload process, or null if the operation is successful. - */ - fun upload( - data: ByteBuffer? = null, - transfer: () -> Unit, - ): Throwable? { - // Bind PBO to unpack the data into whatever the transfer function does - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) - - // Copy pixels to whatever this function does - // Use offset instead of pointer - transfer() - - // Swap buffer - index = (index + 1) % buffers - - // Bind PBO to update pixel source - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]) - - // Map the buffer into the client's memory - val mappedBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT) - - return if (mappedBuffer != null) { - // Check if we're not mapping out of bounds - if (mappedBuffer.capacity().toLong() != size) - return IllegalStateException("The mapped buffer doesn't match the size! Mapping out of bounds?") - - // Update data directly on the mapped buffer - if (data == null || data.limit() == 8) mappedBuffer.put(Random.nextBytes(width)) // Missingno - else mappedBuffer.put(data) - - // Release the buffer - if (!glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) - return IllegalStateException("An unknown error occurred due to GPU memory availability.") - - // Unbind the PBO - // Once bound with 0, all pixel operations behave normal ways. - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) - - // No errors :) - null - } else IllegalStateException("Failed to map buffer, possibly due to insufficient virtual memory.") - } - - /** - * Cleans up resources by deleting the PBOs when the object is no longer in use. - */ - protected fun finalize() { - runGameScheduled { - glDeleteBuffers(pboIds) - } - } - - /** - * Initializes the PBOs, allocates memory for them, and handles unsupported PBO scenarios. - * - * @throws IllegalArgumentException If the number of buffers is less than 1. - * @throws UnsupportedOperationException If the machine doesn't support PBOs. - */ - init { - init() - - // 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, size, bufferUsage.gl) - } - - // Unbind the buffer - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt new file mode 100644 index 000000000..aff396b12 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -0,0 +1,139 @@ +package com.lambda.graphics.buffer.pixel + +import com.lambda.graphics.buffer.IBuffer +import com.lambda.graphics.gl.Memory.padding +import com.lambda.graphics.gl.putTo +import com.lambda.graphics.texture.Texture +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 upload (map) data efficiently. + * + * **Process**: + * Every function that performs a pixel transfer operation can use buffer objects instead of client memory. + * Functions that perform an upload operation, a pixel unpack, will use the buffer object bound to the target GL_PIXEL_UNPACK_BUFFER. + * If a buffer is bound, then the pointer value that those functions take is not a pointer, but an offset from the beginning of that buffer. + * + * @property width The width of the texture + * @property height The height of the texture + * @property texture The [Texture] instance + * @property format The image format that will be uploaded + * + * @see Pixel Buffer Object + */ +class PixelBuffer( + private val width: Int, + private val height: Int, + private val texture: Texture, + private val format: Int, +) : IBuffer { + override val buffers: Int = 2 + override val usage: Int = GL_STATIC_DRAW + override val target: Int = GL_PIXEL_UNPACK_BUFFER + override val access: Int = GL_MAP_WRITE_BIT or GL_MAP_COHERENT_BIT + override var index = 0 + override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } + + private val channels = channelMapping[format] ?: throw IllegalArgumentException("Image format unsupported") + private val internalFormat = reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported") + private val size = width * height * channels * 1L + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? { + // Bind PBO to unpack the data into the texture + bind() + + // Bind the texture and PBO + glBindTexture(GL_TEXTURE_2D, texture.id) + + // Copy pixels from PBO to texture object + // Use offset instead of pointer + glTexSubImage2D( + GL_TEXTURE_2D, // Target + 0, // Mipmap level + 0, 0, // x and y offset + width, height, // width and height of the texture (set to your size) + format, // Format (depends on your data) + GL_UNSIGNED_BYTE, // Type (depends on your data) + 0, // PBO offset (for asynchronous transfer) + ) + + // Unbind the texture + glBindTexture(GL_TEXTURE_2D, 0) + + // Swap the buffer + swap() + + // Bind PBO to update pixel source + bind() + + // Map the buffer into the client's memory + val error = map(offset, size, data::putTo) + + // Unbind + bind(0) + + return error + } + + init { + // Bind the texture + glBindTexture(GL_TEXTURE_2D, texture.id) + + // Calculate memory padding in the case we are using tightly + // packed data in order to save memory and satisfy the computer's + // architecture memory alignment + // https://en.wikipedia.org/wiki/Data_structure_alignment + // In this case we calculate the padding and subtract this to 4 + // in order to tell the padding size + glPixelStorei(GL_UNPACK_ALIGNMENT, 4 - padding(channels)) + + // Allocate texture storage + // TODO: Might want to figure out the data type based on the input + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, 0) + + // Set the texture parameters + 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) + + // Fill the buffers with null data to allocate the memory spaces + grow(size) + + // Unbind the buffer + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + } + + companion object { + /** + * Returns how many channels are used for each image format + */ + private val channelMapping = mapOf( + GL_RED to 1, + GL_GREEN to 1, + GL_BLUE to 1, + GL_ALPHA to 1, + GL_RG to 2, + GL_RGB to 3, + GL_BGR to 3, + GL_RGBA to 4, + GL_BGRA to 4, + ) + + /** + * Returns an internal format based on how many channels there are + */ + private val reverseChannelMapping = mapOf( + 1 to GL_RED, + 2 to GL_RG, + 3 to GL_RGB, + 4 to GL_RGBA, + ) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt deleted file mode 100644 index ccec90409..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt +++ /dev/null @@ -1,227 +0,0 @@ -package com.lambda.graphics.buffer.vao - -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 -import com.lambda.graphics.gl.Memory.address -import com.lambda.graphics.gl.Memory.byteBuffer -import com.lambda.graphics.gl.Memory.capacity -import com.lambda.graphics.gl.Memory.color -import com.lambda.graphics.gl.Memory.copy -import com.lambda.graphics.gl.Memory.float -import com.lambda.graphics.gl.Memory.int -import com.lambda.graphics.gl.Memory.vec2 -import com.lambda.graphics.gl.Memory.vec3 -import com.lambda.graphics.gl.VaoUtils -import com.lambda.graphics.gl.VaoUtils.bindIndexBuffer -import com.lambda.graphics.gl.VaoUtils.bindVertexArray -import com.lambda.graphics.gl.VaoUtils.bindVertexBuffer -import com.lambda.graphics.gl.VaoUtils.bufferData -import com.lambda.graphics.gl.VaoUtils.unbindIndexBuffer -import com.lambda.graphics.gl.VaoUtils.unbindVertexArray -import com.lambda.graphics.gl.VaoUtils.unbindVertexBuffer -import com.lambda.threading.runGameScheduled -import org.joml.* -import org.lwjgl.opengl.GL30C.* -import java.awt.Color -import java.nio.ByteBuffer - -class VAO( - private val vertexMode: VertexMode, - attribGroup: VertexAttrib.Group, - private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC -) : IRenderContext { - private var vao = 0 - private var vbo = 0 - private var ibo = 0 - - private val objectSize: Int - - private var vertices: ByteBuffer - private var verticesPointer: Long - private var verticesPosition: Long - - private var indices: ByteBuffer - private var indicesPointer: Long - private var indicesCount = 0 - private var uploadedIndices = 0 - - private var vertexIndex = 0 - - init { - val stride = attribGroup.stride - objectSize = stride * vertexMode.indicesCount - - vertices = byteBuffer(objectSize * 256 * 4) - verticesPointer = address(vertices) - verticesPosition = verticesPointer - indices = byteBuffer(vertexMode.indicesCount * 512 * 4) - indicesPointer = address(indices) - - vao = glGenVertexArrays() - bindVertexArray(vao) - - vbo = glGenBuffers() - bindVertexBuffer(vbo) - - ibo = glGenBuffers() - bindIndexBuffer(ibo) - - var pointer = 0L - attribGroup.attributes.forEachIndexed { index, attrib -> - VaoUtils.enableVertexAttribute(index) - VaoUtils.vertexAttribute(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) - pointer += attrib.size - } - - unbindVertexArray() - unbindVertexBuffer() - unbindIndexBuffer() - } - - override fun vec3(x: Double, y: Double, z: Double): VAO { - verticesPosition += vec3(verticesPosition, x, y, z) - return this - } - - override fun vec2(x: Double, y: Double): VAO { - verticesPosition += vec2(verticesPosition, x, y) - return this - } - - override fun vec3m(x: Double, y: Double, z: Double): IRenderContext { - Matrices.vertexTransformer?.let { mat -> - val vec = Vector4d(x, y, z, 1.0).apply(mat::transform) - vec3(vec.x, vec.y, vec.z) - } ?: vec3(x, y, z) - - return this - } - - override fun vec2m(x: Double, y: Double): IRenderContext { - Matrices.vertexTransformer?.let { mat -> - val vec = Vector4d(x, y, 0.0, 1.0).apply(mat::transform) - vec2(vec.x, vec.y) - } ?: vec2(x, y) - return this - } - - override fun float(v: Double): VAO { - verticesPosition += float(verticesPosition, v) - return this - } - - override fun color(color: Color): VAO { - verticesPosition += color(verticesPosition, color) - return this - } - - override fun end() = vertexIndex++ - - override fun putLine(vertex1: Int, vertex2: Int) { - growIndices(2) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - indicesCount += 2 - } - - override fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) { - growIndices(3) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - int(p + 8, vertex3) - indicesCount += 3 - } - - override fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) { - growIndices(6) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - int(p + 8, vertex3) - int(p + 12, vertex3) - int(p + 16, vertex4) - int(p + 20, vertex1) - indicesCount += 6 - } - - override fun grow(amount: Int) { - val cap = vertices.capacity - if ((vertexIndex + amount + 1) * objectSize < cap) return - - val offset = verticesPosition - verticesPointer - var newSize = cap * 2 - if (newSize % objectSize != 0) newSize += newSize % objectSize - val newVertices = byteBuffer(newSize) - - val from = address(vertices) - val to = address(newVertices) - copy(from, to, offset) - - vertices = newVertices - verticesPointer = address(vertices) - verticesPosition = verticesPointer + offset - } - - private fun growIndices(amount: Int) { - val cap = indices.capacity - if ((indicesCount + amount) * 4 < cap) return - - var newSize = cap * 2 - if (newSize % vertexMode.indicesCount != 0) newSize += newSize % (vertexMode.indicesCount * 4) - val newIndices = byteBuffer(newSize) - - val from = address(indices) - val to = address(newIndices) - copy(from, to, indicesCount * 4L) - - indices = newIndices - indicesPointer = address(indices) - } - - override fun render() { - if (uploadedIndices <= 0) return - - bindVertexArray(vao) - glDrawElements(vertexMode.gl, uploadedIndices, GL_UNSIGNED_INT, 0) - unbindVertexArray() - } - - override fun upload() { - if (indicesCount <= 0) return - - val vboData = vertices.limit((verticesPosition - verticesPointer).toInt()) - val iboData = indices.limit(indicesCount * 4) - - bindVertexBuffer(vbo) - bufferData(GL_ARRAY_BUFFER, vboData, bufferUsage.gl) - unbindVertexBuffer() - - bindIndexBuffer(ibo) - bufferData(GL_ELEMENT_ARRAY_BUFFER, iboData, bufferUsage.gl) - unbindIndexBuffer() - - uploadedIndices = indicesCount - } - - override fun clear() { - verticesPosition = verticesPointer - vertexIndex = 0 - indicesCount = 0 - uploadedIndices = 0 - } - - protected fun finalize() { - runGameScheduled { - glDeleteBuffers(ibo) - glDeleteBuffers(vbo) - glDeleteVertexArrays(vao) - } - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt new file mode 100644 index 000000000..cf4b96a90 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -0,0 +1,40 @@ +package com.lambda.graphics.buffer.vertex + +import com.lambda.graphics.buffer.IBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.VaoUtils +import com.lambda.graphics.gl.putTo +import org.lwjgl.opengl.GL30C.* +import java.nio.ByteBuffer + +class ElementBuffer(mode: VertexMode) : IBuffer { + override val buffers: Int = 1 + override val usage: Int = GL_DYNAMIC_DRAW + override val target: Int = GL_ELEMENT_ARRAY_BUFFER + override val access: Int = GL_MAP_WRITE_BIT + override var index = 0 + override val bufferIds = IntArray(buffers).also { glGenBuffers(it) } + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? { + // Bind the buffer + bind() + + // Map the buffer into the client's memory + val error = map(offset, data.limit().toLong(), data::putTo) + + // Unbind + bind(0) + + return error + } + + override fun bind(id: Int) = super.bind(VaoUtils.lastIbo) + + init { + // Fill the buffer with null data + grow(mode.indicesCount * 512 * 4L) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt new file mode 100644 index 000000000..bb50eec31 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt @@ -0,0 +1,30 @@ +package com.lambda.graphics.buffer.vertex + +import com.lambda.graphics.buffer.IBuffer +import net.minecraft.client.render.BufferRenderer +import org.lwjgl.opengl.GL30C.* +import java.nio.ByteBuffer + +class VertexArray : IBuffer { + override val buffers: Int = 1 + override val usage: Int = -1 + override val target: Int = -1 + override val access: Int = -1 + override var index = 0 + override val bufferIds = IntArray(buffers).also { glGenVertexArrays(it) } + + override fun map( + offset: Long, + size: Long, + block: (ByteBuffer) -> Unit + ): Throwable? = throw UnsupportedOperationException("Cannot map a vertex array object to memory") + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? = throw UnsupportedOperationException("Data cannot be uploaded to a vertex array object") + + override fun grow(size: Long) = throw UnsupportedOperationException("Cannot grow a vertex array object") + + override fun bind(id: Int) { glBindVertexArray(id); BufferRenderer.currentVertexBuffer = null } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt new file mode 100644 index 000000000..89b1bb2ed --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -0,0 +1,45 @@ +package com.lambda.graphics.buffer.vertex + +import com.lambda.graphics.buffer.IBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.putTo +import org.lwjgl.opengl.GL30C.* +import org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT +import java.nio.ByteBuffer + +class VertexBuffer( + mode: VertexMode, + attributes: VertexAttrib.Group, +) : IBuffer { + override val buffers: Int = 2 + override val usage: Int = GL_DYNAMIC_DRAW + override val target: Int = GL_ARRAY_BUFFER + override val access: Int = GL_MAP_WRITE_BIT or GL_MAP_COHERENT_BIT // TODO: Remove the implicit synchronization ? + override var index = 0 + override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } + + override fun upload(data: ByteBuffer, offset: Long): Throwable? { + // We need to swap the index because our memory mapping requires + // synchronization between the GPU and CPU + // The GL_MAP_COHERENT bit tells OpenGL to synchronize the transfer + // to the buffer + swap() + + // Bind the buffer + bind() + + // Map the buffer into the client's memory + val error = map(offset, data.limit().toLong(), data::putTo) + + // Unbind + bind(0) + + return error + } + + init { + // Fill the buffer with null data + grow(attributes.stride * mode.indicesCount * 256 * 4L) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexAttrib.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt similarity index 95% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexAttrib.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt index 399416eac..83eff1dba 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexAttrib.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt @@ -1,4 +1,4 @@ -package com.lambda.graphics.buffer.vao.vertex +package com.lambda.graphics.buffer.vertex.attributes import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_FLOAT @@ -28,4 +28,4 @@ enum class VertexAttrib(val componentCount: Int, componentSize: Int, val normali val stride = attributes.sumOf { attribute -> attribute.size } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexMode.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt similarity index 82% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexMode.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt index 5a157bc06..0ebd24b71 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexMode.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt @@ -1,4 +1,4 @@ -package com.lambda.graphics.buffer.vao.vertex +package com.lambda.graphics.buffer.vertex.attributes import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_LINES @@ -7,4 +7,4 @@ import org.lwjgl.opengl.GL11C.GL_TRIANGLES enum class VertexMode(val indicesCount: Int, override val gl: Int) : GLObject { LINES(2, GL_LINES), TRIANGLES(3, GL_TRIANGLES) -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt index 84d50fae6..519550bb7 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt @@ -1,6 +1,6 @@ package com.lambda.graphics.gl -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil import java.awt.Color @@ -60,4 +60,22 @@ object Memory { } val Buffer.capacity get() = capacity() -} \ No newline at end of file + + /** + * Returns memory alignment for each CPU architecture + */ + fun alignment(): Int { + return when (System.getProperty("os.arch")?.lowercase()) { + "x86", "x86_64" -> 4 // 32-bit or 64-bit x86 + "arm", "armv7l", "aarch64" -> 4 // ARM architectures + else -> 8 // Default to 8 bytes alignment for other architectures + } + } + + /** + * Returns how many bytes will be added to reach memory alignment + */ + fun padding(size: Int): Int = size % alignment() / 8 +} + +fun ByteBuffer.putTo(dst: ByteBuffer) { dst.put(this) } diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt b/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt index a633d479b..b82a68642 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt @@ -1,43 +1,6 @@ package com.lambda.graphics.gl -import net.minecraft.client.render.BufferRenderer -import org.lwjgl.opengl.GL30C.* -import java.nio.ByteBuffer - object VaoUtils { @JvmField var lastIbo = 0 - private var prevIbo = 0 - - fun bindVertexArray(vao: Int) { - glBindVertexArray(vao) - BufferRenderer.currentVertexBuffer = null - } - - fun bindVertexBuffer(vbo: Int) = - glBindBuffer(GL_ARRAY_BUFFER, vbo) - - fun bindIndexBuffer(ibo: Int) { - if (ibo != 0) prevIbo = lastIbo - val targetIbo = if (ibo != 0) ibo else prevIbo - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, targetIbo) - } - - fun unbindVertexArray() = - bindVertexArray(0) - - fun unbindVertexBuffer() = - bindVertexBuffer(0) - - fun unbindIndexBuffer() = - bindIndexBuffer(0) - - fun enableVertexAttribute(i: Int) = - glEnableVertexAttribArray(i) - - fun vertexAttribute(index: Int, size: Int, type: Int, normalized: Boolean, stride: Int, pointer: Long) = - glVertexAttribPointer(index, size, type, normalized, stride, pointer) - - fun bufferData(target: Int, data: ByteBuffer, usage: Int) = - glBufferData(target, data, usage) } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index 1d1f8ef2c..35059a613 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -5,7 +5,6 @@ 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.BufferUsage import com.lambda.graphics.renderer.esp.impl.ESPRenderer import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer import com.lambda.module.modules.client.RenderSettings @@ -102,17 +101,17 @@ class ChunkedESP private constructor( } suspend fun rebuild() { - val newRenderer = awaitMainThread { - StaticESPRenderer(BufferUsage.STATIC) - } + awaitMainThread { + val newRenderer = StaticESPRenderer() - iterateChunk { x, y, z -> - owner.update(newRenderer, chunk.world, x, y, z) - } + iterateChunk { x, y, z -> + owner.update(newRenderer, chunk.world, x, y, z) + } - owner.uploadQueue.add { - newRenderer.upload() - renderer = newRenderer + owner.uploadQueue.add { + newRenderer.upload() + renderer = newRenderer + } } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt index a0a157e50..dd59dea31 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt @@ -2,7 +2,6 @@ package com.lambda.graphics.renderer.esp.builders import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection -import com.lambda.graphics.renderer.esp.impl.ESPRenderer import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer import com.lambda.util.extension.max import com.lambda.util.extension.min diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt index fa40ed0ca..2c10cd927 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt @@ -4,10 +4,9 @@ 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.BufferUsage import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer -object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) { +object StaticESP : StaticESPRenderer(false) { init { listener { clear() diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt index e9383f035..9d1b599e0 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt @@ -1,5 +1,3 @@ package com.lambda.graphics.renderer.esp.impl -import com.lambda.graphics.buffer.BufferUsage - -open class DynamicESPRenderer : ESPRenderer(BufferUsage.DYNAMIC, true) +open class DynamicESPRenderer : ESPRenderer(true) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt index 15d0977da..0c483c8f1 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt @@ -1,30 +1,26 @@ package com.lambda.graphics.renderer.esp.impl import com.lambda.Lambda.mc -import com.lambda.graphics.buffer.vao.VAO -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.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.GlStateUtils.withFaceCulling import com.lambda.graphics.gl.GlStateUtils.withLineWidth import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.RenderSettings import com.lambda.util.extension.partialTicks -abstract class ESPRenderer( - usage: BufferUsage, - tickedMode: Boolean -) { +abstract class ESPRenderer(tickedMode: Boolean) { val shader: Shader - val faces: VAO - val outlines: VAO + val faces: VertexPipeline + val outlines: VertexPipeline init { val mode = if (tickedMode) dynamicMode else staticMode shader = mode.first - faces = VAO(VertexMode.TRIANGLES, mode.second, usage) - outlines = VAO(VertexMode.LINES, mode.second, usage) + faces = VertexPipeline(VertexMode.TRIANGLES, mode.second) + outlines = VertexPipeline(VertexMode.LINES, mode.second) } open fun upload() { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt index 592412dde..b9868b869 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt @@ -1,14 +1,10 @@ package com.lambda.graphics.renderer.esp.impl -import com.lambda.graphics.buffer.vao.IRenderContext -import com.lambda.graphics.buffer.BufferUsage +import com.lambda.graphics.buffer.IRenderContext import java.awt.Color import java.util.concurrent.ConcurrentHashMap -open class StaticESPRenderer( - usage: BufferUsage = BufferUsage.STATIC, - private val useVertexCaching: Boolean = true, -) : ESPRenderer(usage, false) { +open class StaticESPRenderer(private val useVertexCaching: Boolean = true) : ESPRenderer(false) { val faceVertices = ConcurrentHashMap() val outlineVertices = ConcurrentHashMap() diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt index bdf0342ab..9f9b176bc 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt @@ -1,19 +1,18 @@ package com.lambda.graphics.renderer.gui import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.shader.Shader import com.lambda.graphics.texture.Texture -import com.lambda.graphics.video.Video import com.lambda.module.modules.client.GuiSettings import com.lambda.util.math.Rect import com.lambda.util.math.Vec2d import org.lwjgl.glfw.GLFW.glfwGetTime object TextureRenderer { - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) + private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private val shader = Shader("renderer/pos_tex") private val shaderColored = Shader("renderer/pos_tex_shady") diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt index a4fc18e1f..5ead2b378 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt @@ -1,8 +1,8 @@ package com.lambda.graphics.renderer.gui.font -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.renderer.gui.font.glyph.GlyphInfo import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.LambdaMoji @@ -16,7 +16,7 @@ class FontRenderer( private val font: LambdaFont, private val emojis: LambdaEmoji ) { - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) + private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) var scaleMultiplier = 1.0 diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt index 6f70a3e37..03c7cb1a5 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt @@ -1,9 +1,9 @@ package com.lambda.graphics.renderer.gui.rect import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.GuiSettings import com.lambda.util.math.Vec2d @@ -13,7 +13,7 @@ abstract class AbstractRectRenderer( attribGroup: VertexAttrib.Group, val shader: Shader ) { - protected val vao = VAO(VertexMode.TRIANGLES, attribGroup) + protected val vao = VertexPipeline(VertexMode.TRIANGLES, attribGroup) fun render() { shader.use() @@ -27,4 +27,4 @@ abstract class AbstractRectRenderer( vao.render() vao.clear() } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt index c19f128b1..740e0cf9c 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt @@ -1,6 +1,6 @@ package com.lambda.graphics.renderer.gui.rect -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.shader.Shader import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Rect @@ -63,4 +63,4 @@ class FilledRectRenderer : AbstractRectRenderer( private val shader = Shader("renderer/rect_filled") } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt index 3405a26fc..bdeeab966 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt @@ -1,7 +1,7 @@ package com.lambda.graphics.renderer.gui.rect -import com.lambda.graphics.buffer.vao.IRenderContext -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.IRenderContext +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.shader.Shader import com.lambda.util.math.lerp import com.lambda.util.math.MathUtils.toInt diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt b/common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt deleted file mode 100644 index 082e3520e..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/AVDecoder.kt +++ /dev/null @@ -1,259 +0,0 @@ -package com.lambda.graphics.video - -import org.bytedeco.ffmpeg.avcodec.AVCodecContext -import org.bytedeco.ffmpeg.avcodec.AVPacket -import org.bytedeco.ffmpeg.avformat.AVFormatContext -import org.bytedeco.ffmpeg.global.avcodec.* -import org.bytedeco.ffmpeg.global.avformat.* -import org.bytedeco.ffmpeg.global.avutil.* -import org.bytedeco.ffmpeg.global.swscale.* -import org.bytedeco.ffmpeg.swscale.SwsContext -import org.bytedeco.javacpp.BytePointer -import org.bytedeco.javacpp.DoublePointer -import org.bytedeco.javacpp.PointerPointer -import java.nio.ByteBuffer - -class AVDecoder( - path: String, -) { - private val formatContext: AVFormatContext = AVFormatContext(null) - private var videoCodecContext: AVCodecContext? = null - private var audioCodecContext: AVCodecContext? = null - private var videoStreamIndex: Int = -1 - private var audioStreamIndex: Int = -1 - private var swsContext: SwsContext? = null - - private var error = -1 - private val errorBuffer = ByteArray(1024) - - private var videoPTS = 0L - - fun videoInfo(): VideoInfo { - val fps = av_q2d(formatContext.streams(videoStreamIndex).r_frame_rate()) - val timeBase = av_q2d(formatContext.streams(videoStreamIndex).time_base()) - - return VideoInfo( - width = videoCodecContext?.width() ?: 0, - height = videoCodecContext?.height() ?: 0, - frameRate = fps, - frameDuration = { videoPTS * timeBase }, - ) - } - - fun audioInfo(): AudioInfo { - return AudioInfo(0) - } - - fun audioFrameIterator(): Iterator? { - audioCodecContext?.let { codec -> - val audioFrame = av_frame_alloc() - val pkt = AVPacket() - - // Number of bytes per sample or zero if unknown for the given sample format - // https://ffmpeg.org/doxygen/3.3/group__lavu__sampfmts.html#ga0c3c218e1dd570ad4917c69a35a6c77d - val bufferOutputSize = - av_get_bytes_per_sample(codec.sample_fmt()) - - return iterator { - while (av_read_frame(formatContext, pkt) >= 0) { - if (pkt.stream_index() == audioStreamIndex) { - avcodec_send_packet(codec, pkt) - error = avcodec_receive_frame(codec, audioFrame) - } - - if (error >= 0) { - val decodedAudio = - ByteBuffer.allocate(bufferOutputSize * codec.ch_layout().nb_channels()) - - for (ch in 0 until codec.ch_layout().nb_channels()) { - decodedAudio.put( - audioFrame.data(ch).position(bufferOutputSize.toLong()) - .position(bufferOutputSize.toLong()) - .asByteBuffer() - ) - } - - yield(decodedAudio) - } - - // Unreferences the buffer referenced by the packet and - // reset the remaining packet fields to their default - // values - // https://ffmpeg.org/doxygen/trunk/group__lavc__packet.html#ga63d5a489b419bd5d45cfd09091cbcbc2 - av_packet_unref(pkt) - } - - // Clean up to prevent memory leak - av_frame_free(audioFrame) - } - } - - return null - } - - fun videoFrameIterator( - pixelFormat: Int = AV_PIX_FMT_RGB24, - ): Iterator? { - videoCodecContext?.let { codec -> - val pkt = AVPacket() - - val videoFrame = av_frame_alloc() - val pFrameRGB = av_frame_alloc() - - // Returns the size in bytes of the amount of data required - // to store an image - // https://ffmpeg.org/doxygen/trunk/group__lavu__picture.html#ga24a67963c3ae0054a2a4bab35930e694 - val bufferOutputSize = - av_image_get_buffer_size( - pixelFormat, - codec.width(), - codec.height(), - 1, - ).toLong() - - val buffer = BytePointer(av_malloc(bufferOutputSize)) - - // Map deprecated pixel formats to their newest compatible - // versions - val sourcePixFmt = correctForDeprecatedPixelFormat(codec.pix_fmt()) - - // Allocate and return an SwsContext - // https://ffmpeg.org/doxygen/trunk/group__libsws.html#gaf360d1a9e0e60f906f74d7d44f9abfdd - if (swsContext == null) { - swsContext = sws_getContext( - codec.width(), - codec.height(), - sourcePixFmt, - codec.width(), - codec.height(), - pixelFormat, - SWS_BILINEAR, - null, null, null as DoublePointer? - ) - } - - // Setup the data pointers and linesizes based on the image - // https://ffmpeg.org/doxygen/trunk/group__lavu__picture.html#ga5b6ead346a70342ae8a303c16d2b3629 - av_image_fill_arrays( - pFrameRGB.data(), // Destination - pFrameRGB.linesize(), // Destination Linesize - buffer, // Source buffer - pixelFormat, // Pixel Format - codec.width(), // Width - codec.height(), // Height - 1, // Alignment - ) - - return iterator { - while (av_read_frame(formatContext, pkt) >= 0) { - if (pkt.stream_index() == videoStreamIndex) { - avcodec_send_packet(codec, pkt) - error = avcodec_receive_frame(codec, videoFrame) - } - - videoPTS = videoFrame.pts() - - if (error >= 0) { - // Scale the image slice into the destination image - // https://www.ffmpeg.org/doxygen/2.7/group__libsws.html#gae531c9754c9205d90ad6800015046d74 - sws_scale( - swsContext, // Scaling context - videoFrame.data(), // Source slice - videoFrame.linesize(), // Source stride - 0, // Position in the source image of the slice to process - videoFrame.height(), - pFrameRGB.data(), - pFrameRGB.linesize(), // Destination stride - ) - - // We need to set the capacity in order to use - // `asByteBuffer()` - val convertedFrame = - pFrameRGB.data(0).capacity(bufferOutputSize).asByteBuffer() - - yield(convertedFrame) - } - - // Unreferences the buffer referenced by the packet and - // reset the remaining packet fields to their default - // values - // https://ffmpeg.org/doxygen/trunk/group__lavc__packet.html#ga63d5a489b419bd5d45cfd09091cbcbc2 - av_packet_unref(pkt) - } - - // Clean up to prevent memory leak - av_frame_free(videoFrame) - av_frame_free(pFrameRGB) - } - } - - return null - } - - private fun correctForDeprecatedPixelFormat(pixFmt: Int): Int { - // Fix swscaler deprecated pixel format warning - // (YUVJ has been deprecated, change pixel format to regular YUV) - return when (pixFmt) { - AV_PIX_FMT_YUVJ420P -> AV_PIX_FMT_YUV420P - AV_PIX_FMT_YUVJ422P -> AV_PIX_FMT_YUV422P - AV_PIX_FMT_YUVJ444P -> AV_PIX_FMT_YUV444P - AV_PIX_FMT_YUVJ440P -> AV_PIX_FMT_YUV440P - else -> pixFmt - } - } - - fun close() { - videoCodecContext?.close() - audioCodecContext?.close() - formatContext.close() - } - - init { - error = avformat_open_input(formatContext, path, null, null) - if (error < 0) { - av_strerror(error, errorBuffer, 1024) - throw IllegalStateException("Failed to open video file at $path: ${errorBuffer.decodeToString()}") - } - - error = avformat_find_stream_info(formatContext, null as PointerPointer<*>?) - if (error < 0) { - av_strerror(error, errorBuffer, 1024) - throw IllegalStateException("Failed to find stream info for video file at $path: ${errorBuffer.decodeToString()}") - } - - av_dump_format(formatContext, 0, path, 0) - - // Setup audio and video codecs - for (i in 0 until formatContext.nb_streams()) { - val stream = formatContext.streams(i) - - when (stream.codecpar().codec_type()) { - AVMEDIA_TYPE_VIDEO -> { - if (videoStreamIndex >= 0) continue - - // Allocate video codec context and set codec parameters - videoCodecContext = avcodec_alloc_context3(null) - avcodec_parameters_to_context(videoCodecContext, stream.codecpar()) - - val codec = avcodec_find_decoder(videoCodecContext?.codec_id() ?: 0) - avcodec_open2(videoCodecContext, codec, null as PointerPointer<*>?) - - videoStreamIndex = i - } - - AVMEDIA_TYPE_AUDIO -> { - if (audioStreamIndex >= 0) continue - - // Allocate audio codec context and set codec parameters - audioCodecContext = avcodec_alloc_context3(null) - avcodec_parameters_to_context(audioCodecContext, stream.codecpar()) - - val codec = avcodec_find_decoder(audioCodecContext?.codec_id() ?: 0) - avcodec_open2(audioCodecContext, codec, null as PointerPointer<*>?) - - audioStreamIndex = i - } - } - } - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt b/common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt deleted file mode 100644 index a57945d63..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/AudioInfo.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.lambda.graphics.video - -data class AudioInfo( - val a: Int, -) diff --git a/common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt b/common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt deleted file mode 100644 index 1af73101a..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/SampleFormats.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.lambda.graphics.video - -import org.bytedeco.ffmpeg.global.avutil.* -import java.nio.ByteOrder - -enum class SampleFormats( - val sample: Int, - val formatBe: String, - val formatLe: String, -) { - U8(AV_SAMPLE_FMT_U8, "u8", "u8"), - S16(AV_SAMPLE_FMT_S16, "s16be", "s16le"), - S32(AV_SAMPLE_FMT_S32, "s32be", "s32le"), - F32(AV_SAMPLE_FMT_FLT, "f32be", "f32le"), - F64(AV_SAMPLE_FMT_DBL, "f64be", "f64le"); - - fun AV_NE(): String { - return if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) formatLe - else formatBe - } - - companion object { - fun getSampleFromFormat(format: Int): SampleFormats { - return SampleFormats.entries.find { it.sample == format } - ?: throw IllegalArgumentException("Unknown format: $format") - } - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt b/common/src/main/kotlin/com/lambda/graphics/video/Video.kt deleted file mode 100644 index 6f7d4f238..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/Video.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.lambda.graphics.video - -import com.lambda.Lambda.LOG -import com.lambda.graphics.buffer.pbo.PixelBuffer -import com.lambda.graphics.texture.Texture -import org.bytedeco.ffmpeg.avcodec.AVCodecContext -import org.bytedeco.ffmpeg.avformat.AVFormatContext -import org.lwjgl.opengl.GL45C.* -import java.nio.ByteBuffer - -class Video( - private val videoInfo: VideoInfo, - private val audioInfo: AudioInfo, - private val videoIterator: Iterator?, - private val audioIterator: Iterator?, -) : Texture() { - val width = videoInfo.width - val height = videoInfo.height - - private val videoBuffer: ByteBuffer - get() = if (videoIterator?.hasNext() == true) videoIterator.next() else ByteBuffer.allocate(0) - - private val audioBuffer: ByteBuffer - get() = if (audioIterator?.hasNext() == true) audioIterator.next() else ByteBuffer.allocate(0) - - private val pbo = PixelBuffer(width, height, channels = 3) { - // Bind the texture - glBindTexture(GL_TEXTURE_2D, id) - - // Tell OpenGL that we are using tightly packed data - // If we don't do this, the alignment will truncate - // to 16 bytes because we only have 24 bytes and computers - // don't like this - glPixelStorei(GL_UNPACK_ALIGNMENT, 1) - - // Allocate texture storage - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0) - - // Set the texture parameters - 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) - } - - private var lastTime = System.currentTimeMillis() - private var delta = 0L - - fun transfer() { - with(pbo) { - delta = System.currentTimeMillis() - lastTime / 1000 - - if (delta >= videoInfo.frameDuration()) { - upload(videoBuffer) { - // Bind the texture and PBO - glBindTexture(GL_TEXTURE_2D, id) - - // Copy pixels from PBO to texture object - // Use offset instead of pointer - glTexSubImage2D( - GL_TEXTURE_2D, // Target - 0, // Mipmap level - 0, 0, // x and y offset - width, height, // width and height of the texture (set to your size) - GL_RGB, // Format (depends on your data) - GL_UNSIGNED_BYTE, // Type (depends on your data) - 0 // PBO offset (for asynchronous transfer) - ) - - // Unbind the texture - glBindTexture(GL_TEXTURE_2D, 0) - }?.let(LOG::error) - - lastTime = System.currentTimeMillis() - } - } - } - - companion object { - /** - * Retrieves a video from the resources' folder. - * - * @param path The path to the image. - */ - fun fromResource(path: String): Video { - val decoder = AVDecoder(path) - - return Video( - decoder.videoInfo(), - decoder.audioInfo(), - decoder.videoFrameIterator(), - decoder.audioFrameIterator(), - ) - } - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt b/common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt deleted file mode 100644 index 1e5395b8e..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/video/VideoInfo.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.lambda.graphics.video - -data class VideoInfo( - val width: Int, - val height: Int, - val frameRate: Double, - val frameDuration: () -> Double, -) diff --git a/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt b/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt deleted file mode 100644 index 3bbf659ad..000000000 --- a/common/src/main/kotlin/com/lambda/module/hud/PBOExample.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.lambda.module.hud - -import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture -import com.lambda.graphics.video.Video -import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag - -object PBOExample : HudModule( - name = "PBOExample", - description = "Test the pbo impl", - defaultTags = setOf(ModuleTag.CLIENT), -) { - override val width: Double - get() = video.width.toDouble() - override val height: Double - get() = video.height.toDouble() - - private val video = Video.fromResource("C:\\Users\\Kamigen\\Downloads\\pizza.mp4") - - init { - onRender { - video.transfer() - - drawTexture(video, rect) - } - } -} diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt index 392da9db9..4e5b3d62d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt @@ -7,9 +7,9 @@ import com.lambda.event.events.MovementEvent 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.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.GlStateUtils.withBlendFunc import com.lambda.graphics.gl.GlStateUtils.withDepth import com.lambda.graphics.gl.Matrices @@ -63,7 +63,7 @@ object Particles : Module( private val environmentSpeedV by setting("E Speed V", 0.1, 0.0..10.0, 0.1) { environment } private var particles = mutableListOf() - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) + private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) private val shader = Shader("renderer/particle", "renderer/particle") init { From e3c8a8a0a86952462bfb2e1efb304765cee75871 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:49:39 -0400 Subject: [PATCH 30/45] ref: buffer check and ebo fix --- .../mixin/render/VertexBufferMixin.java | 4 ++-- .../com/lambda/graphics/buffer/IBuffer.kt | 13 +++++++--- .../graphics/buffer/vertex/ElementBuffer.kt | 16 ++++++++++--- .../graphics/buffer/vertex/VertexArray.kt | 2 +- .../kotlin/com/lambda/graphics/gl/Buffers.kt | 24 +++++++++++++++++++ .../kotlin/com/lambda/graphics/gl/VaoUtils.kt | 6 ----- 6 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt delete mode 100644 common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt diff --git a/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java b/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java index 6c3584660..d8c787099 100644 --- a/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java @@ -1,6 +1,6 @@ package com.lambda.mixin.render; -import com.lambda.graphics.gl.VaoUtils; +import com.lambda.graphics.buffer.vertex.ElementBuffer; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gl.VertexBuffer; import net.minecraft.client.render.BufferBuilder; @@ -20,6 +20,6 @@ public class VertexBufferMixin { @Inject(method = "uploadIndexBuffer", at = @At("RETURN")) private void onConfigureIndexBuffer(BufferBuilder.DrawParameters parameters, ByteBuffer vertexBuffer, CallbackInfoReturnable cir) { RenderSystem.ShapeIndexBuffer value = cir.getReturnValue(); - VaoUtils.lastIbo = value == null ? this.indexBufferId : value.id; + ElementBuffer.lastIbo = value == null ? this.indexBufferId : value.id; } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index 2d2b69b7e..0c6df9a8d 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -1,5 +1,7 @@ package com.lambda.graphics.buffer +import com.lambda.graphics.gl.bindingCheckMappings +import com.sun.tools.javac.main.Option.G import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer @@ -131,9 +133,14 @@ interface IBuffer { block: (ByteBuffer) -> Unit ): Throwable? { if( - target <= 0 + target < 34962 || + bindingCheckMappings[target] == null ) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + if ( + glGetIntegeri(target, bindingCheckMappings.getValue(target)) == GL_FALSE + ) return IllegalArgumentException("Target buffer is not bound") + if( offset < 0 || size < 0 @@ -144,8 +151,8 @@ interface IBuffer { ) return IllegalArgumentException("Out of bound mapping: $offset + $size > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") if( - glGetBufferParameteri(target, GL_BUFFER_SIZE) == GL_TRUE - ) return IllegalStateException("Buffer is already mapped, make sure to follow the documentation") + glGetBufferParameteri(target, GL_BUFFER_MAPPED) == GL_TRUE + ) return IllegalStateException("Buffer is already mapped, something wrong happened") if( access and GL_MAP_WRITE_BIT == 0 && diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt index cf4b96a90..3ef293d1a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -2,7 +2,6 @@ package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer import com.lambda.graphics.buffer.vertex.attributes.VertexMode -import com.lambda.graphics.gl.VaoUtils import com.lambda.graphics.gl.putTo import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer @@ -13,7 +12,7 @@ class ElementBuffer(mode: VertexMode) : IBuffer { override val target: Int = GL_ELEMENT_ARRAY_BUFFER override val access: Int = GL_MAP_WRITE_BIT override var index = 0 - override val bufferIds = IntArray(buffers).also { glGenBuffers(it) } + override val bufferIds = intArrayOf(glGenBuffers()) override fun upload( data: ByteBuffer, @@ -31,10 +30,21 @@ class ElementBuffer(mode: VertexMode) : IBuffer { return error } - override fun bind(id: Int) = super.bind(VaoUtils.lastIbo) + override fun bind(id: Int) { + if (id != 0) prevIbo = lastIbo + val target = if (id != 0) id else prevIbo + + super.bind(target) + } init { // Fill the buffer with null data grow(mode.indicesCount * 512 * 4L) } + + companion object { + @JvmField + var lastIbo = 0 + var prevIbo = 0 + } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt index bb50eec31..783aad862 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt @@ -11,7 +11,7 @@ class VertexArray : IBuffer { override val target: Int = -1 override val access: Int = -1 override var index = 0 - override val bufferIds = IntArray(buffers).also { glGenVertexArrays(it) } + override val bufferIds = intArrayOf(glGenVertexArrays()) override fun map( offset: Long, diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt new file mode 100644 index 000000000..428cae48c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt @@ -0,0 +1,24 @@ +package com.lambda.graphics.gl + +import org.lwjgl.opengl.GL44.* + +/** + * Map of valid buffer binding target to their respective binding check parameter + * using [glGetIntegeri] + */ +val bindingCheckMappings = mapOf( + GL_ARRAY_BUFFER to GL_ARRAY_BUFFER_BINDING, + GL_ATOMIC_COUNTER_BUFFER to GL_ATOMIC_COUNTER_BUFFER_BINDING, + GL_COPY_READ_BUFFER_BINDING to GL_COPY_READ_BUFFER_BINDING, + GL_COPY_WRITE_BUFFER_BINDING to GL_COPY_WRITE_BUFFER_BINDING, + GL_DISPATCH_INDIRECT_BUFFER to GL_DISPATCH_INDIRECT_BUFFER_BINDING, + GL_DRAW_INDIRECT_BUFFER to GL_DRAW_INDIRECT_BUFFER_BINDING, + GL_ELEMENT_ARRAY_BUFFER to GL_ELEMENT_ARRAY_BUFFER_BINDING, + GL_PIXEL_PACK_BUFFER to GL_PIXEL_PACK_BUFFER_BINDING, + GL_PIXEL_UNPACK_BUFFER to GL_PIXEL_UNPACK_BUFFER_BINDING, + GL_QUERY_BUFFER to GL_QUERY_BUFFER_BINDING, + GL_SHADER_STORAGE_BUFFER to GL_SHADER_STORAGE_BUFFER_BINDING, + GL_TEXTURE_BUFFER to GL_TEXTURE_BUFFER_BINDING, + GL_TRANSFORM_FEEDBACK_BUFFER to GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, + GL_UNIFORM_BUFFER to GL_UNIFORM_BUFFER_BINDING, +) diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt b/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt deleted file mode 100644 index b82a68642..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.lambda.graphics.gl - -object VaoUtils { - @JvmField - var lastIbo = 0 -} From b471b39c5ca1af01faf74a181920fea2179d576b Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:36:52 -0400 Subject: [PATCH 31/45] renamed vpipeline variables names --- .../kotlin/com/lambda/graphics/buffer/FrameBuffer.kt | 10 +++++----- .../lambda/graphics/renderer/gui/TextureRenderer.kt | 10 +++++----- .../lambda/graphics/renderer/gui/font/FontRenderer.kt | 10 +++++----- .../graphics/renderer/gui/rect/AbstractRectRenderer.kt | 8 ++++---- .../graphics/renderer/gui/rect/FilledRectRenderer.kt | 2 +- .../graphics/renderer/gui/rect/OutlineRectRenderer.kt | 2 +- .../com/lambda/module/modules/render/Particles.kt | 10 +++++----- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt index 60add450f..60eb88fe9 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt @@ -49,7 +49,7 @@ class FrameBuffer(private val depth: Boolean = false) { shader.use() shaderBlock(shader) - vao.use { + pipeline.use { grow(4) val uv1 = pos1 / RenderMain.screenSize @@ -64,9 +64,9 @@ class FrameBuffer(private val depth: Boolean = false) { } withBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) { - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } return this @@ -121,7 +121,7 @@ class FrameBuffer(private val depth: Boolean = false) { } companion object { - private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private var lastFrameBuffer: Int? = null } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt index 9f9b176bc..f0968e5a4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt @@ -12,7 +12,7 @@ import com.lambda.util.math.Vec2d import org.lwjgl.glfw.GLFW.glfwGetTime object TextureRenderer { - private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private val shader = Shader("renderer/pos_tex") private val shaderColored = Shader("renderer/pos_tex_shady") @@ -39,7 +39,7 @@ object TextureRenderer { val pos1 = rect.leftTop val pos2 = rect.rightBottom - vao.use { + pipeline.use { grow(4) putQuad( @@ -50,8 +50,8 @@ object TextureRenderer { ) } - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt index 5ead2b378..acadb70a5 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt @@ -16,7 +16,7 @@ class FontRenderer( private val font: LambdaFont, private val emojis: LambdaEmoji ) { - private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) var scaleMultiplier = 1.0 @@ -29,7 +29,7 @@ class FontRenderer( color: Color = Color.WHITE, scale: Double = 1.0, shadow: Boolean = true, - ) = vao.use { + ) = pipeline.use { iterateText(text, scale, shadow, color) { char, pos1, pos2, color -> grow(4) putQuad( @@ -146,9 +146,9 @@ class FontRenderer( font.glyphs.bind() emojis.glyphs.bind() - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt index 03c7cb1a5..079499e9b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt @@ -13,7 +13,7 @@ abstract class AbstractRectRenderer( attribGroup: VertexAttrib.Group, val shader: Shader ) { - protected val vao = VertexPipeline(VertexMode.TRIANGLES, attribGroup) + protected val pipeline = VertexPipeline(VertexMode.TRIANGLES, attribGroup) fun render() { shader.use() @@ -23,8 +23,8 @@ abstract class AbstractRectRenderer( shader["u_Size"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt index 740e0cf9c..068147245 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt @@ -25,7 +25,7 @@ class FilledRectRenderer : AbstractRectRenderer( rightBottom: Color = Color.WHITE, leftBottom: Color = Color.WHITE, shade: Boolean = false, - ) = vao.use { + ) = pipeline.use { val pos1 = rect.leftTop val pos2 = rect.rightBottom diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt index bdeeab966..789664226 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt @@ -36,7 +36,7 @@ class OutlineRectRenderer : AbstractRectRenderer( rightBottom: Color = Color.WHITE, leftBottom: Color = Color.WHITE, shade: Boolean = false, - ) = vao.use { + ) = pipeline.use { if (glowRadius < 1) return@use grow(verticesCount * 3) diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt index 4e5b3d62d..60be77204 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt @@ -63,7 +63,7 @@ object Particles : Module( private val environmentSpeedV by setting("E Speed V", 0.1, 0.0..10.0, 0.1) { environment } private var particles = mutableListOf() - private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) private val shader = Shader("renderer/particle", "renderer/particle") init { @@ -80,9 +80,9 @@ object Particles : Module( shader.use() shader["u_CameraPosition"] = mc.gameRenderer.camera.pos - vao.upload() - withDepth(vao::render) - vao.clear() + pipeline.upload() + withDepth(pipeline::render) + pipeline.clear() } } @@ -182,7 +182,7 @@ object Particles : Module( val size = if (lay) environmentSize else sizeSetting * lerp(alpha, 0.5, 1.0) withVertexTransform(buildWorldProjection(position, size, projRotation)) { - vao.use { + pipeline.use { grow(4) // DO NOT FUCKING FORGOTEOIJTOWKET TO GROW (cost me an hour) putQuad( vec3m(-1.0, -1.0, 0.0).vec2(0.0, 0.0).color(color).end(), From b06eba09db27076786ea3b094f81d6f5477c26af Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:05:05 -0400 Subject: [PATCH 32/45] fix: minecraft buffer overwrite --- .../src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index 0c6df9a8d..a4c7abf55 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -114,8 +114,9 @@ interface IBuffer { fun grow(size: Long) { bufferIds.forEach { bufferId -> // Orphan the buffer and allocate a new one - glBindBuffer(target, bufferId) + bind(bufferId) glBufferData(target, size, usage) + bind(0) } } @@ -138,8 +139,8 @@ interface IBuffer { ) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if ( - glGetIntegeri(target, bindingCheckMappings.getValue(target)) == GL_FALSE - ) return IllegalArgumentException("Target buffer is not bound") + glGetIntegeri(bindingCheckMappings.getValue(target), index) == GL_FALSE + ) return IllegalArgumentException("Target is zero bound") if( offset < 0 || From 593fffbe0264b0e5ec6f686a0d5ee71cf71793da Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:43:29 -0400 Subject: [PATCH 33/45] fix: glMapBufferRange returns null --- .../com/lambda/graphics/buffer/IBuffer.kt | 71 ++++++++++++++----- .../graphics/buffer/pixel/PixelBuffer.kt | 3 - .../graphics/buffer/vertex/VertexBuffer.kt | 14 ++-- .../kotlin/com/lambda/graphics/gl/Buffers.kt | 19 ++++- 4 files changed, 78 insertions(+), 29 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index a4c7abf55..836b2f5e6 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -1,7 +1,8 @@ package com.lambda.graphics.buffer -import com.lambda.graphics.gl.bindingCheckMappings -import com.sun.tools.javac.main.Option.G +import com.lambda.graphics.gl.bufferValid +import com.lambda.graphics.gl.bufferBound +import com.lambda.graphics.gl.bufferUsageValid import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer @@ -105,19 +106,55 @@ interface IBuffer { */ fun swap() { index = (index + 1) % buffers } + /** + * Update the current buffer without re-allocating + * Alternative to [map] + */ + fun update( + data: ByteBuffer, + offset: Long, + ): Throwable? { + if(!bufferValid(target)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferBound(target)) + return IllegalArgumentException("Target is zero bound for glBufferSubData") + + glBufferSubData(target, offset, data) + + return null + } + /** * Grows the backing buffers * This function should not be called frequently * * @param size The size of the new buffer */ - fun grow(size: Long) { + fun grow(size: Long): Throwable? { + if( + size < 0 + ) return IllegalArgumentException("Invalid size parameter: $size") + + // FixMe: If access contains any of GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT and the buffer was not initialized using glBufferStorage, glMapBufferRange will fail + if(!bufferValid(target)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + bufferIds.forEach { bufferId -> // Orphan the buffer and allocate a new one bind(bufferId) - glBufferData(target, size, usage) - bind(0) + + // Only resize if the new size is bigger than the bound buffer capacity + if (size > glGetBufferParameteri(target, GL_BUFFER_SIZE)) + glBufferData(target, size, usage) + + bind(0) // Don't forget to unbind to avoid accidental buffer modification } + + return null } /** @@ -133,26 +170,24 @@ interface IBuffer { size: Long, block: (ByteBuffer) -> Unit ): Throwable? { - if( - target < 34962 || - bindingCheckMappings[target] == null - ) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if ( - glGetIntegeri(bindingCheckMappings.getValue(target), index) == GL_FALSE - ) return IllegalArgumentException("Target is zero bound") - if( offset < 0 || size < 0 ) return IllegalArgumentException("Invalid offset or size parameter offset: $offset size: $size") + if(!bufferValid(target)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferBound(target)) + return IllegalArgumentException("Target is zero bound for glMapBufferRange") + if( offset + size > glGetBufferParameteri(target, GL_BUFFER_SIZE) ) return IllegalArgumentException("Out of bound mapping: $offset + $size > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") if( - glGetBufferParameteri(target, GL_BUFFER_MAPPED) == GL_TRUE + glGetBufferParameteri(target, GL_BUFFER_MAPPED) + == GL_TRUE ) return IllegalStateException("Buffer is already mapped, something wrong happened") if( @@ -169,15 +204,15 @@ interface IBuffer { ) return IllegalArgumentException("GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set.") // Map the buffer into the client's memory - val sharedRegion = glMapBufferRange(target, offset, size.toLong(), access) - ?: return IllegalStateException("Failed to map buffer, possibly due to insufficient virtual memory.") + val sharedRegion = glMapBufferRange(target, offset, size, access) + ?: return IllegalStateException("Failed to map buffer") // Update data on the shared buffer block(sharedRegion) // Release the buffer if (!glUnmapBuffer(target)) - return IllegalStateException("An unknown error occurred due to GPU memory availability.") + return IllegalStateException("An unknown error occurred due to GPU memory availability of buffer corruption") return null } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt index aff396b12..8c98a62d3 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -105,9 +105,6 @@ class PixelBuffer( // Fill the buffers with null data to allocate the memory spaces grow(size) - - // Unbind the buffer - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt index 89b1bb2ed..ca3e35b1e 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -15,23 +15,23 @@ class VertexBuffer( override val buffers: Int = 2 override val usage: Int = GL_DYNAMIC_DRAW override val target: Int = GL_ARRAY_BUFFER - override val access: Int = GL_MAP_WRITE_BIT or GL_MAP_COHERENT_BIT // TODO: Remove the implicit synchronization ? + override val access: Int = GL_MAP_WRITE_BIT override var index = 0 override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } override fun upload(data: ByteBuffer, offset: Long): Throwable? { + // Bind the buffer + bind() + + // Update the buffer data + val error = map(offset, data.limit().toLong(), data::putTo) + // We need to swap the index because our memory mapping requires // synchronization between the GPU and CPU // The GL_MAP_COHERENT bit tells OpenGL to synchronize the transfer // to the buffer swap() - // Bind the buffer - bind() - - // Map the buffer into the client's memory - val error = map(offset, data.limit().toLong(), data::putTo) - // Unbind bind(0) diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt index 428cae48c..4ef297ae3 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt @@ -4,7 +4,7 @@ import org.lwjgl.opengl.GL44.* /** * Map of valid buffer binding target to their respective binding check parameter - * using [glGetIntegeri] + * using [bufferBound] */ val bindingCheckMappings = mapOf( GL_ARRAY_BUFFER to GL_ARRAY_BUFFER_BINDING, @@ -22,3 +22,20 @@ val bindingCheckMappings = mapOf( GL_TRANSFORM_FEEDBACK_BUFFER to GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, GL_UNIFORM_BUFFER to GL_UNIFORM_BUFFER_BINDING, ) + +/** + * Returns whether the buffer target is valid + */ +fun bufferValid(target: Int): Boolean = target in bindingCheckMappings + +/** + * Returns whether the provided buffer target is bound + */ +fun bufferBound(target: Int): Boolean = + IntArray(1) + .apply { glGetIntegerv(bindingCheckMappings.getValue(target), this) }[0] != GL_FALSE + +/** + * Returns whether the provided buffer usage is valid + */ +fun bufferUsageValid(usage: Int) = usage >= GL_STREAM_DRAW && usage <= GL_DYNAMIC_DRAW From 2f80a5314eaf0c7a81d106141eb761790e808507 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 19 Oct 2024 10:15:03 -0400 Subject: [PATCH 34/45] unused --- .../kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt index ca3e35b1e..89ffc6ee8 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -5,7 +5,6 @@ import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.putTo import org.lwjgl.opengl.GL30C.* -import org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT import java.nio.ByteBuffer class VertexBuffer( From 3ff6bc6b71c3ac723725691d6a8e71d61159e540 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:35:46 -0400 Subject: [PATCH 35/45] ref: memory utils --- .../lambda/graphics/buffer/VertexPipeline.kt | 45 +++---- .../graphics/buffer/pixel/PixelBuffer.kt | 2 +- .../graphics/buffer/vertex/ElementBuffer.kt | 3 +- .../graphics/buffer/vertex/VertexBuffer.kt | 3 +- .../kotlin/com/lambda/graphics/gl/Memory.kt | 114 ++++++++++++------ 5 files changed, 102 insertions(+), 65 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt index 95e60871e..a3042e059 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -10,9 +10,10 @@ import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Memory import com.lambda.graphics.gl.Memory.address import com.lambda.graphics.gl.Memory.byteBuffer -import com.lambda.graphics.gl.Memory.capacity -import com.lambda.graphics.gl.Memory.copy import com.lambda.graphics.gl.Memory.int +import com.lambda.graphics.gl.Memory.vector2f +import com.lambda.graphics.gl.Memory.vector3f +import com.lambda.graphics.gl.kibibyte import org.joml.Vector4d import org.lwjgl.opengl.GL20C.* import java.awt.Color @@ -28,11 +29,11 @@ class VertexPipeline( private val vbo = VertexBuffer(mode, attributes) private val ebo = ElementBuffer(mode) - private var vertices = byteBuffer(size * 256 * 4) + private var vertices = byteBuffer(size * 1.kibibyte) private var verticesPointer = address(vertices) private var verticesPosition = verticesPointer - private var indices = byteBuffer(mode.indicesCount * 512 * 4) + private var indices = byteBuffer(mode.indicesCount * 2.kibibyte) private var indicesPointer = address(indices) private var indicesCount = 0 private var uploadedIndices = 0 @@ -40,12 +41,12 @@ class VertexPipeline( private var vertexIndex = 0 override fun vec3(x: Double, y: Double, z: Double): VertexPipeline { - verticesPosition += Memory.vec3(verticesPosition, x, y, z) + verticesPosition += vector3f(verticesPosition, x, y, z) return this } override fun vec2(x: Double, y: Double): VertexPipeline { - verticesPosition += Memory.vec2(verticesPosition, x, y) + verticesPosition += vector2f(verticesPosition, x, y) return this } @@ -89,29 +90,29 @@ class VertexPipeline( override fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) { growIndices(3) - val p = indicesPointer + indicesCount * 4L + val position = indicesPointer + indicesCount * 4L - int(p + 0, vertex1) - int(p + 4, vertex2) - int(p + 8, vertex3) + int(position + 0, vertex1) + int(position + 4, vertex2) + int(position + 8, vertex3) indicesCount += 3 } override fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) { growIndices(6) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - int(p + 8, vertex3) - int(p + 12, vertex3) - int(p + 16, vertex4) - int(p + 20, vertex1) + val position = indicesPointer + indicesCount * 4L + + int(position + 0, vertex1) + int(position + 4, vertex2) + int(position + 8, vertex3) + int(position + 12, vertex3) + int(position + 16, vertex4) + int(position + 20, vertex1) indicesCount += 6 } override fun grow(amount: Int) { - val cap = vertices.capacity + val cap = vertices.capacity() if ((vertexIndex + amount + 1) * size < cap) return val offset = verticesPosition - verticesPointer @@ -121,7 +122,7 @@ class VertexPipeline( val from = address(vertices) val to = address(newVertices) - copy(from, to, offset) + Memory.copy(from, to, offset) vbo.grow(newSize.toLong()) @@ -131,7 +132,7 @@ class VertexPipeline( } private fun growIndices(amount: Int) { - val cap = indices.capacity + val cap = indices.capacity() if ((indicesCount + amount) * 4 < cap) return var newSize = cap * 2 @@ -140,7 +141,7 @@ class VertexPipeline( val from = address(indices) val to = address(newIndices) - copy(from, to, indicesCount * 4L) + Memory.copy(from, to, indicesCount * 4L) ebo.grow(newSize.toLong()) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt index 8c98a62d3..2845894a0 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -1,7 +1,7 @@ package com.lambda.graphics.buffer.pixel import com.lambda.graphics.buffer.IBuffer -import com.lambda.graphics.gl.Memory.padding +import com.lambda.graphics.gl.padding import com.lambda.graphics.gl.putTo import com.lambda.graphics.texture.Texture import org.lwjgl.opengl.GL45C.* diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt index 3ef293d1a..72f09de27 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -2,6 +2,7 @@ package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.kibibyte import com.lambda.graphics.gl.putTo import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer @@ -39,7 +40,7 @@ class ElementBuffer(mode: VertexMode) : IBuffer { init { // Fill the buffer with null data - grow(mode.indicesCount * 512 * 4L) + grow(mode.indicesCount * 2L.kibibyte) } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt index 89ffc6ee8..98013dc52 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -3,6 +3,7 @@ package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.kibibyte import com.lambda.graphics.gl.putTo import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer @@ -39,6 +40,6 @@ class VertexBuffer( init { // Fill the buffer with null data - grow(attributes.stride * mode.indicesCount * 256 * 4L) + grow(attributes.stride * mode.indicesCount * 1L.kibibyte) } } diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt index 519550bb7..5b198561c 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt @@ -1,81 +1,115 @@ package com.lambda.graphics.gl -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import org.lwjgl.system.MemoryUtil.memPutByte +import org.lwjgl.system.MemoryUtil.memPutFloat +import org.lwjgl.system.MemoryUtil.memPutInt import java.awt.Color import java.nio.Buffer import java.nio.ByteBuffer object Memory { - private val floatSize = VertexAttrib.Float.size - private val vec2Size = VertexAttrib.Vec2.size - private val vec3Size = VertexAttrib.Vec3.size - private val colorSize = VertexAttrib.Color.size - - fun vec2(address: Long, x: Double, y: Double): Int { + /** + * Puts a float for each axis at the current buffer position + */ + fun vector2f(address: Long, x: Double, y: Double): Int { float(address + 0, x) float(address + 4, y) - return vec2Size + return 8 } - fun vec3(address: Long, x: Double, y: Double, z: Double): Int { + /** + * Puts a float for each axis at the current buffer position + */ + fun vector3f(address: Long, x: Double, y: Double, z: Double): Int { float(address + 0, x) float(address + 4, y) float(address + 8, z) - return vec3Size + return 12 } + /** + * Puts a byte for each color channel at the current buffer position + */ fun color(address: Long, color: Color): Int { byte(address + 0, color.red.toByte()) byte(address + 1, color.green.toByte()) byte(address + 2, color.blue.toByte()) byte(address + 3, color.alpha.toByte()) - return colorSize + return 4 } - private fun byte(address: Long, value: Byte) { - MemoryUtil.memPutByte(address, value) + /** + * Puts a byte at the current buffer position + */ + fun byte(address: Long, value: Byte): Int { + memPutByte(address, value) + return 1 } - fun int(address: Long, value: Int) { - MemoryUtil.memPutInt(address, value) + /** + * Puts an integer at the current buffer position + */ + fun int(address: Long, value: Int): Int { + memPutInt(address, value) + return 4 } + /** + * Puts a float at the current buffer position + */ fun float(address: Long, value: Double): Int { - MemoryUtil.memPutFloat(address, value.toFloat()) - return floatSize - } - - fun address(buffer: Buffer): Long { - return MemoryUtil.memAddress0(buffer) + memPutFloat(address, value.toFloat()) + return 4 } - fun copy(from: Long, to: Long, bytes: Long) { - MemoryUtil.memCopy(from, to, bytes) - } - - fun byteBuffer(cap: Int): ByteBuffer { - return BufferUtils.createByteBuffer(cap) - } - - val Buffer.capacity get() = capacity() + /** + * Returns the address of the first element within the buffer + */ + fun address(buffer: Buffer): Long = MemoryUtil.memAddress0(buffer) /** - * Returns memory alignment for each CPU architecture + * Copies [bytes] bytes from [src] to [dst] */ - fun alignment(): Int { - return when (System.getProperty("os.arch")?.lowercase()) { - "x86", "x86_64" -> 4 // 32-bit or 64-bit x86 - "arm", "armv7l", "aarch64" -> 4 // ARM architectures - else -> 8 // Default to 8 bytes alignment for other architectures - } - } + fun copy(src: Long, dst: Long, bytes: Long) = MemoryUtil.memCopy(src, dst, bytes) /** - * Returns how many bytes will be added to reach memory alignment + * Creates a new buffer of [cap] bytes */ - fun padding(size: Int): Int = size % alignment() / 8 + fun byteBuffer(cap: Int) = BufferUtils.createByteBuffer(cap) } +val Int.kilobyte get() = this / 1000 +val Int.megabyte get() = this / 1000 / 1000 +val Int.gigabyte get() = this / 1000 / 1000 / 1000 + +val Int.kibibyte get() = this / 1024 +val Int.mebibyte get() = this / 1024 / 1024 +val Int.gibibyte get() = this / 1024 / 1024 / 1024 + +val Long.kilobyte get() = this / 1000 +val Long.megabyte get() = this / 1000 / 1000 +val Long.gigabyte get() = this / 1000 / 1000 / 1000 + +val Long.kibibyte get() = this / 1024 +val Long.mebibyte get() = this / 1024 / 1024 +val Long.gibibyte get() = this / 1024 / 1024 / 1024 + +/** + * Returns memory alignment for each CPU architecture + */ +fun alignment(): Int { + return when (System.getProperty("os.arch")?.lowercase()) { + "x86", "x86_64" -> 4 // 32-bit or 64-bit x86 + "arm", "armv7l", "aarch64" -> 4 // ARM architectures + else -> 8 // Default to 8 bytes alignment for other architectures + } +} + +/** + * Returns how many bytes will be added to reach memory alignment + */ +fun padding(size: Int): Int = size % alignment() / 8 + fun ByteBuffer.putTo(dst: ByteBuffer) { dst.put(this) } From fface98cdaf26ac8570f300ba066439de8d6f556 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:51:18 -0400 Subject: [PATCH 36/45] fix: byte conversion --- .../kotlin/com/lambda/graphics/gl/Memory.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt index 5b198561c..5677b1659 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt @@ -80,21 +80,21 @@ object Memory { fun byteBuffer(cap: Int) = BufferUtils.createByteBuffer(cap) } -val Int.kilobyte get() = this / 1000 -val Int.megabyte get() = this / 1000 / 1000 -val Int.gigabyte get() = this / 1000 / 1000 / 1000 +val Int.kilobyte get() = this * 1000 +val Int.megabyte get() = this * 1000 * 1000 +val Int.gigabyte get() = this * 1000 * 1000 * 1000 -val Int.kibibyte get() = this / 1024 -val Int.mebibyte get() = this / 1024 / 1024 -val Int.gibibyte get() = this / 1024 / 1024 / 1024 +val Int.kibibyte get() = this * 1024 +val Int.mebibyte get() = this * 1024 * 1024 +val Int.gibibyte get() = this * 1024 * 1024 * 1024 -val Long.kilobyte get() = this / 1000 -val Long.megabyte get() = this / 1000 / 1000 -val Long.gigabyte get() = this / 1000 / 1000 / 1000 +val Long.kilobyte get() = this * 1000 +val Long.megabyte get() = this * 1000 * 1000 +val Long.gigabyte get() = this * 1000 * 1000 * 1000 -val Long.kibibyte get() = this / 1024 -val Long.mebibyte get() = this / 1024 / 1024 -val Long.gibibyte get() = this / 1024 / 1024 / 1024 +val Long.kibibyte get() = this * 1024 +val Long.mebibyte get() = this * 1024 * 1024 +val Long.gibibyte get() = this * 1024 * 1024 * 1024 /** * Returns memory alignment for each CPU architecture From 807b5d26f80c5df5ca7eac49936ab4bcd2844cbb Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:51:38 -0400 Subject: [PATCH 37/45] ref: buffer growth --- .../lambda/graphics/buffer/VertexPipeline.kt | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt index a3042e059..0f58063cb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -14,6 +14,7 @@ import com.lambda.graphics.gl.Memory.int import com.lambda.graphics.gl.Memory.vector2f import com.lambda.graphics.gl.Memory.vector3f import com.lambda.graphics.gl.kibibyte +import com.lambda.graphics.gl.putTo import org.joml.Vector4d import org.lwjgl.opengl.GL20C.* import java.awt.Color @@ -112,17 +113,14 @@ class VertexPipeline( } override fun grow(amount: Int) { - val cap = vertices.capacity() - if ((vertexIndex + amount + 1) * size < cap) return + val requiredCapacity = (vertexIndex + amount + 1) * size + if (requiredCapacity < vertices.capacity()) return val offset = verticesPosition - verticesPointer - var newSize = cap * 2 - if (newSize % size != 0) newSize += newSize % size - val newVertices = byteBuffer(newSize) + val newSize = vertices.capacity().let { it * 2 + it % size } - val from = address(vertices) - val to = address(newVertices) - Memory.copy(from, to, offset) + val newVertices = byteBuffer(newSize) + Memory.copy(address(vertices), address(newVertices), offset) vbo.grow(newSize.toLong()) @@ -132,16 +130,13 @@ class VertexPipeline( } private fun growIndices(amount: Int) { - val cap = indices.capacity() - if ((indicesCount + amount) * 4 < cap) return + val requiredCapacity = (indicesCount + amount) * 4 + if (requiredCapacity < indices.capacity()) return - var newSize = cap * 2 - if (newSize % mode.indicesCount != 0) newSize += newSize % (mode.indicesCount * 4) + val newSize = indices.capacity().let { it * 2 + it % (mode.indicesCount * 4) } val newIndices = byteBuffer(newSize) - val from = address(indices) - val to = address(newIndices) - Memory.copy(from, to, indicesCount * 4L) + Memory.copy(address(indices), address(newIndices), indicesCount * 4L) ebo.grow(newSize.toLong()) From ff8d28eb2d4379f3f2bbeed383e19b0c9b76a604 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:23:42 -0400 Subject: [PATCH 38/45] copyright notices --- .../lambda/graphics/animation/Animation.kt | 2 +- .../com/lambda/graphics/buffer/IBuffer.kt | 74 +++++++++++++------ .../lambda/graphics/buffer/IRenderContext.kt | 2 +- .../lambda/graphics/buffer/VertexPipeline.kt | 18 ++++- .../graphics/buffer/pixel/PixelBuffer.kt | 20 ++++- .../graphics/buffer/vertex/ElementBuffer.kt | 17 +++++ .../graphics/buffer/vertex/VertexArray.kt | 21 +++++- .../graphics/buffer/vertex/VertexBuffer.kt | 17 +++++ .../buffer/vertex/attributes/VertexAttrib.kt | 2 +- .../buffer/vertex/attributes/VertexMode.kt | 2 +- .../kotlin/com/lambda/graphics/gl/Buffers.kt | 17 +++++ .../kotlin/com/lambda/graphics/gl/Memory.kt | 17 +++++ 12 files changed, 178 insertions(+), 31 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt b/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt index 19b9a9fff..f9a13eea9 100644 --- a/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt +++ b/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt @@ -18,8 +18,8 @@ package com.lambda.graphics.animation import com.lambda.Lambda.mc -import com.lambda.util.math.lerp import com.lambda.util.extension.partialTicks +import com.lambda.util.math.lerp import kotlin.math.abs import kotlin.reflect.KProperty diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index 836b2f5e6..05265a300 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -1,8 +1,25 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.buffer -import com.lambda.graphics.gl.bufferValid import com.lambda.graphics.gl.bufferBound import com.lambda.graphics.gl.bufferUsageValid +import com.lambda.graphics.gl.bufferValid import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer @@ -104,17 +121,19 @@ interface IBuffer { /** * Swaps the buffer [index] if [buffers] is greater than 1 */ - fun swap() { index = (index + 1) % buffers } + fun swap() { + index = (index + 1) % buffers + } /** * Update the current buffer without re-allocating * Alternative to [map] */ fun update( - data: ByteBuffer, + data: ByteBuffer, offset: Long, ): Throwable? { - if(!bufferValid(target)) + if (!bufferValid(target)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferBound(target)) @@ -132,12 +151,12 @@ interface IBuffer { * @param size The size of the new buffer */ fun grow(size: Long): Throwable? { - if( - size < 0 + if ( + size < 0 ) return IllegalArgumentException("Invalid size parameter: $size") // FixMe: If access contains any of GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT and the buffer was not initialized using glBufferStorage, glMapBufferRange will fail - if(!bufferValid(target)) + if (!bufferValid(target)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferUsageValid(usage)) @@ -167,40 +186,47 @@ interface IBuffer { */ fun map( offset: Long, - size: Long, - block: (ByteBuffer) -> Unit + size: Long, + block: (ByteBuffer) -> Unit ): Throwable? { - if( + if ( offset < 0 || - size < 0 + size < 0 ) return IllegalArgumentException("Invalid offset or size parameter offset: $offset size: $size") - if(!bufferValid(target)) + if (!bufferValid(target)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferBound(target)) return IllegalArgumentException("Target is zero bound for glMapBufferRange") - if( + if ( offset + size > glGetBufferParameteri(target, GL_BUFFER_SIZE) - ) return IllegalArgumentException("Out of bound mapping: $offset + $size > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") - - if( + ) return IllegalArgumentException( + "Out of bound mapping: $offset + $size > ${ + glGetBufferParameteri( + target, + GL_BUFFER_SIZE + ) + }" + ) + + if ( glGetBufferParameteri(target, GL_BUFFER_MAPPED) == GL_TRUE ) return IllegalStateException("Buffer is already mapped, something wrong happened") - if( + if ( access and GL_MAP_WRITE_BIT == 0 && - access and GL_MAP_READ_BIT == 0 + access and GL_MAP_READ_BIT == 0 ) return IllegalArgumentException("Neither GL_MAP_READ_BIT nor GL_MAP_WRITE_BIT is set") - if( - access and GL_MAP_READ_BIT != 0 && - (access and GL_MAP_INVALIDATE_RANGE_BIT == 0 || - access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || - access and GL_MAP_UNSYNCHRONIZED_BIT == 0 - ) + if ( + access and GL_MAP_READ_BIT != 0 && + (access and GL_MAP_INVALIDATE_RANGE_BIT == 0 || + access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || + access and GL_MAP_UNSYNCHRONIZED_BIT == 0 + ) ) return IllegalArgumentException("GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set.") // Map the buffer into the client's memory diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt index efa9de588..3fb28a33b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.buffer.vao +package com.lambda.graphics.buffer import java.awt.Color diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt index 0f58063cb..1704053e3 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.buffer import com.lambda.Lambda.LOG @@ -14,7 +31,6 @@ import com.lambda.graphics.gl.Memory.int import com.lambda.graphics.gl.Memory.vector2f import com.lambda.graphics.gl.Memory.vector3f import com.lambda.graphics.gl.kibibyte -import com.lambda.graphics.gl.putTo import org.joml.Vector4d import org.lwjgl.opengl.GL20C.* import java.awt.Color diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt index 2845894a0..94608d71b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.buffer.pixel import com.lambda.graphics.buffer.IBuffer @@ -37,7 +54,8 @@ class PixelBuffer( override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } private val channels = channelMapping[format] ?: throw IllegalArgumentException("Image format unsupported") - private val internalFormat = reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported") + private val internalFormat = + reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported") private val size = width * height * channels * 1L override fun upload( diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt index 72f09de27..579c4c4c3 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt index 783aad862..adc679fe0 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer @@ -26,5 +43,7 @@ class VertexArray : IBuffer { override fun grow(size: Long) = throw UnsupportedOperationException("Cannot grow a vertex array object") - override fun bind(id: Int) { glBindVertexArray(id); BufferRenderer.currentVertexBuffer = null } + override fun bind(id: Int) { + glBindVertexArray(id); BufferRenderer.currentVertexBuffer = null + } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt index 98013dc52..0b8944388 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt index 2e5367735..729334ac7 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.buffer.vao.vertex +package com.lambda.graphics.buffer.vertex.attributes import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_FLOAT diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt index 886e9b502..e0c6c45df 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.buffer.vao.vertex +package com.lambda.graphics.buffer.vertex.attributes import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_LINES diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt index 4ef297ae3..977c07e45 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.gl import org.lwjgl.opengl.GL44.* diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt index 5677b1659..8b1ec9708 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2024 Lambda + * + * 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 distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.lambda.graphics.gl import org.lwjgl.BufferUtils From 7b37548f6cf361430cb49815b113aeae6c505152 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:18:17 -0400 Subject: [PATCH 39/45] ignore unchecked cast --- common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index 5bebe41df..97c389da3 100644 --- a/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -228,6 +228,7 @@ object WorldUtils { predicate: (FastVector, FluidState) -> Boolean = { _, _ -> true }, iterator: (FastVector, FluidState) -> Unit = { _, _ -> }, ) { + @Suppress("UNCHECKED_CAST") internalIteratePositions(pos, range, step) { position -> world.getFluidState(position.x, position.y, position.z).let { state -> val fulfilled = kClass.isInstance(state.fluid) && predicate(position, state) From beddf730277da371540366ddc3e38ea8ace90a15 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:14:25 -0400 Subject: [PATCH 40/45] ref: buffer orphaning over mapping --- .../com/lambda/graphics/buffer/IBuffer.kt | 59 ++++++++++--------- .../lambda/graphics/buffer/VertexPipeline.kt | 4 -- .../graphics/buffer/pixel/PixelBuffer.kt | 2 +- .../graphics/buffer/vertex/ElementBuffer.kt | 16 +---- .../graphics/buffer/vertex/VertexArray.kt | 2 +- .../graphics/buffer/vertex/VertexBuffer.kt | 29 +++------ .../kotlin/com/lambda/util/extension/Other.kt | 19 ++++++ .../kotlin/com/lambda/util/math/MathUtils.kt | 14 +---- 8 files changed, 63 insertions(+), 82 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index 05265a300..c5a6ed3a4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -144,16 +144,35 @@ interface IBuffer { return null } + /** + * Allocates a region of memory for the buffer + * This function handles the buffer binding + * + * @param data The data to put in the new allocated buffer + */ + fun allocate(data: ByteBuffer): Throwable? { + // FixMe: If access contains any of GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT and the buffer was not initialized using glBufferStorage, glMapBufferRange will fail + if (!bufferValid(target)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferData(target, data, usage) + bind(0) + + return null + } + /** * Grows the backing buffers - * This function should not be called frequently + * This function handles the buffer binding * * @param size The size of the new buffer */ - fun grow(size: Long): Throwable? { - if ( - size < 0 - ) return IllegalArgumentException("Invalid size parameter: $size") + fun allocate(size: Long): Throwable? { + if (size < 0) return IllegalArgumentException("Invalid size parameter: $size") // FixMe: If access contains any of GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT and the buffer was not initialized using glBufferStorage, glMapBufferRange will fail if (!bufferValid(target)) @@ -162,16 +181,9 @@ interface IBuffer { if (!bufferUsageValid(usage)) return IllegalArgumentException("Buffer usage is invalid") - bufferIds.forEach { bufferId -> - // Orphan the buffer and allocate a new one - bind(bufferId) - - // Only resize if the new size is bigger than the bound buffer capacity - if (size > glGetBufferParameteri(target, GL_BUFFER_SIZE)) - glBufferData(target, size, usage) - - bind(0) // Don't forget to unbind to avoid accidental buffer modification - } + bind() + glBufferData(target, size, usage) + bind(0) return null } @@ -202,14 +214,7 @@ interface IBuffer { if ( offset + size > glGetBufferParameteri(target, GL_BUFFER_SIZE) - ) return IllegalArgumentException( - "Out of bound mapping: $offset + $size > ${ - glGetBufferParameteri( - target, - GL_BUFFER_SIZE - ) - }" - ) + ) return IllegalArgumentException("Out of bound mapping: $offset + $size > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") if ( glGetBufferParameteri(target, GL_BUFFER_MAPPED) @@ -222,11 +227,11 @@ interface IBuffer { ) return IllegalArgumentException("Neither GL_MAP_READ_BIT nor GL_MAP_WRITE_BIT is set") if ( - access and GL_MAP_READ_BIT != 0 && + access and GL_MAP_READ_BIT != 0 && (access and GL_MAP_INVALIDATE_RANGE_BIT == 0 || - access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || - access and GL_MAP_UNSYNCHRONIZED_BIT == 0 - ) + access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || + access and GL_MAP_UNSYNCHRONIZED_BIT == 0 + ) ) return IllegalArgumentException("GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set.") // Map the buffer into the client's memory diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt index 1704053e3..cfaa1daef 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -138,8 +138,6 @@ class VertexPipeline( val newVertices = byteBuffer(newSize) Memory.copy(address(vertices), address(newVertices), offset) - vbo.grow(newSize.toLong()) - vertices = newVertices verticesPointer = address(vertices) verticesPosition = verticesPointer + offset @@ -154,8 +152,6 @@ class VertexPipeline( Memory.copy(address(indices), address(newIndices), indicesCount * 4L) - ebo.grow(newSize.toLong()) - indices = newIndices indicesPointer = address(indices) } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt index 94608d71b..8b1445302 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -122,7 +122,7 @@ class PixelBuffer( glBindTexture(GL_TEXTURE_2D, 0) // Fill the buffers with null data to allocate the memory spaces - grow(size) + allocate(size) } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt index 579c4c4c3..0bba1fe5e 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -35,18 +35,7 @@ class ElementBuffer(mode: VertexMode) : IBuffer { override fun upload( data: ByteBuffer, offset: Long, - ): Throwable? { - // Bind the buffer - bind() - - // Map the buffer into the client's memory - val error = map(offset, data.limit().toLong(), data::putTo) - - // Unbind - bind(0) - - return error - } + ): Throwable? = allocate(data) override fun bind(id: Int) { if (id != 0) prevIbo = lastIbo @@ -56,8 +45,7 @@ class ElementBuffer(mode: VertexMode) : IBuffer { } init { - // Fill the buffer with null data - grow(mode.indicesCount * 2L.kibibyte) + allocate(mode.indicesCount * 2L.kibibyte) } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt index adc679fe0..1043f98c9 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt @@ -41,7 +41,7 @@ class VertexArray : IBuffer { offset: Long, ): Throwable? = throw UnsupportedOperationException("Data cannot be uploaded to a vertex array object") - override fun grow(size: Long) = throw UnsupportedOperationException("Cannot grow a vertex array object") + override fun allocate(size: Long) = throw UnsupportedOperationException("Cannot grow a vertex array object") override fun bind(id: Int) { glBindVertexArray(id); BufferRenderer.currentVertexBuffer = null diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt index 0b8944388..91977535d 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -29,34 +29,19 @@ class VertexBuffer( mode: VertexMode, attributes: VertexAttrib.Group, ) : IBuffer { - override val buffers: Int = 2 + override val buffers: Int = 1 override val usage: Int = GL_DYNAMIC_DRAW override val target: Int = GL_ARRAY_BUFFER override val access: Int = GL_MAP_WRITE_BIT override var index = 0 - override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } + override val bufferIds = intArrayOf(glGenBuffers()) - override fun upload(data: ByteBuffer, offset: Long): Throwable? { - // Bind the buffer - bind() - - // Update the buffer data - val error = map(offset, data.limit().toLong(), data::putTo) - - // We need to swap the index because our memory mapping requires - // synchronization between the GPU and CPU - // The GL_MAP_COHERENT bit tells OpenGL to synchronize the transfer - // to the buffer - swap() - - // Unbind - bind(0) - - return error - } + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? = allocate(data) init { - // Fill the buffer with null data - grow(attributes.stride * mode.indicesCount * 1L.kibibyte) + allocate(attributes.stride * mode.indicesCount * 1L.kibibyte) } } diff --git a/common/src/main/kotlin/com/lambda/util/extension/Other.kt b/common/src/main/kotlin/com/lambda/util/extension/Other.kt index 0d1f8079e..9dff4ea5c 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Other.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Other.kt @@ -17,6 +17,25 @@ package com.lambda.util.extension +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Executes the given block only if the object receiver is null + * Opposite of `Any?.let {}` + */ +@OptIn(ExperimentalContracts::class) +inline fun T?.ifNull(block: () -> Unit): T? { + contract { + callsInPlace(block, InvocationKind.AT_MOST_ONCE) + } + + if (this == null) block() + + return this +} + val Class<*>.isObject: Boolean get() = declaredFields.any { it.name == "INSTANCE" } diff --git a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt index 5ff6dcfbc..b397cf190 100644 --- a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt @@ -80,19 +80,7 @@ object MathUtils { return nextDouble(min, max) } - /** - * @return The smallest power of two that is greater than or equal to the input integer. - */ - fun Int.ceilToPOT(): Int { - var i = this - i-- - i = i or (i shr 1) - i = i or (i shr 2) - i = i or (i shr 4) - i = i or (i shr 8) - i = i or (i shr 16) - return ++i - } + fun Int.nextPowerOf2() = 2f.pow(ceil(log2(toFloat()))).toInt() inline val Int.sq: Int get() = this * this } From 33b291dc379d40769169c93f24aa7c68f3215dda Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:25:42 -0400 Subject: [PATCH 41/45] fix: wrong vertex attributes --- .../main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt | 2 +- .../kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt index cfaa1daef..ef886c56b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -196,7 +196,7 @@ class VertexPipeline( glEnableVertexAttribArray(index) glVertexAttribPointer(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) - attrib.size.toLong() + pointer + attrib.size // I'm not sure why there's no accumulator indexed iterator, this cost me many hours } // Unbind everything to avoid accidental modification diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt index 0bba1fe5e..20cc67c34 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -20,7 +20,6 @@ package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.IBuffer import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.kibibyte -import com.lambda.graphics.gl.putTo import org.lwjgl.opengl.GL30C.* import java.nio.ByteBuffer From aa9c7cea7e17fcfe0dd829b7ebc4966bacf64ca0 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:59:19 -0400 Subject: [PATCH 42/45] added glBufferStorage option --- .../com/lambda/graphics/buffer/IBuffer.kt | 45 +++++++++++++++++-- .../graphics/buffer/pixel/PixelBuffer.kt | 7 ++- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index c5a6ed3a4..59416d5df 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -21,6 +21,7 @@ import com.lambda.graphics.gl.bufferBound import com.lambda.graphics.gl.bufferUsageValid import com.lambda.graphics.gl.bufferValid import org.lwjgl.opengl.GL30C.* +import org.lwjgl.opengl.GL44.glBufferStorage import java.nio.ByteBuffer interface IBuffer { @@ -151,7 +152,6 @@ interface IBuffer { * @param data The data to put in the new allocated buffer */ fun allocate(data: ByteBuffer): Throwable? { - // FixMe: If access contains any of GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT and the buffer was not initialized using glBufferStorage, glMapBufferRange will fail if (!bufferValid(target)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") @@ -172,9 +172,46 @@ interface IBuffer { * @param size The size of the new buffer */ fun allocate(size: Long): Throwable? { - if (size < 0) return IllegalArgumentException("Invalid size parameter: $size") + if (!bufferValid(target)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferData(target, size.coerceAtLeast(0), usage) + bind(0) + + return null + } + + /** + * Create a new buffer storage + * This function cannot be called twice for the same buffer + * This function handles the buffer binding + */ + fun storage(data: ByteBuffer): Throwable? { + if (!bufferValid(target)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferStorage(target, data, usage) + bind(0) - // FixMe: If access contains any of GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT and the buffer was not initialized using glBufferStorage, glMapBufferRange will fail + return null + } + + /** + * Create a new buffer storage + * This function cannot be called twice for the same buffer + * This function handles the buffer binding + * + * @param size The size of the storage buffer + */ + fun storage(size: Long): Throwable? { if (!bufferValid(target)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") @@ -182,7 +219,7 @@ interface IBuffer { return IllegalArgumentException("Buffer usage is invalid") bind() - glBufferData(target, size, usage) + glBufferStorage(target, size.coerceAtLeast(0), usage) bind(0) return null diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt index 8b1445302..ef8f7495d 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -54,8 +54,7 @@ class PixelBuffer( override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } private val channels = channelMapping[format] ?: throw IllegalArgumentException("Image format unsupported") - private val internalFormat = - reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported") + private val internalFormat = reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported") private val size = width * height * channels * 1L override fun upload( @@ -121,8 +120,8 @@ class PixelBuffer( // Unbind the texture glBindTexture(GL_TEXTURE_2D, 0) - // Fill the buffers with null data to allocate the memory spaces - allocate(size) + // Fill the storage with null + storage(size) } companion object { From ec2e9d86957b837efaac92b9903cc70ccec07a52 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:06:44 -0400 Subject: [PATCH 43/45] check for buffer access flags --- .../kotlin/com/lambda/graphics/buffer/IBuffer.kt | 12 ++++++------ .../main/kotlin/com/lambda/graphics/gl/Buffers.kt | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt index 59416d5df..9aaeb7a00 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -134,7 +134,7 @@ interface IBuffer { data: ByteBuffer, offset: Long, ): Throwable? { - if (!bufferValid(target)) + if (!bufferValid(target, access)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferBound(target)) @@ -152,7 +152,7 @@ interface IBuffer { * @param data The data to put in the new allocated buffer */ fun allocate(data: ByteBuffer): Throwable? { - if (!bufferValid(target)) + if (!bufferValid(target, access)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferUsageValid(usage)) @@ -172,7 +172,7 @@ interface IBuffer { * @param size The size of the new buffer */ fun allocate(size: Long): Throwable? { - if (!bufferValid(target)) + if (!bufferValid(target, access)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferUsageValid(usage)) @@ -191,7 +191,7 @@ interface IBuffer { * This function handles the buffer binding */ fun storage(data: ByteBuffer): Throwable? { - if (!bufferValid(target)) + if (!bufferValid(target, access)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferUsageValid(usage)) @@ -212,7 +212,7 @@ interface IBuffer { * @param size The size of the storage buffer */ fun storage(size: Long): Throwable? { - if (!bufferValid(target)) + if (!bufferValid(target, access)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferUsageValid(usage)) @@ -243,7 +243,7 @@ interface IBuffer { size < 0 ) return IllegalArgumentException("Invalid offset or size parameter offset: $offset size: $size") - if (!bufferValid(target)) + if (!bufferValid(target, access)) return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") if (!bufferBound(target)) diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt index 977c07e45..c1a1636c9 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt @@ -43,7 +43,12 @@ val bindingCheckMappings = mapOf( /** * Returns whether the buffer target is valid */ -fun bufferValid(target: Int): Boolean = target in bindingCheckMappings +fun bufferValid(target: Int, access: Int): Boolean = + target in bindingCheckMappings && + // If access contains GL_MAP_COHERENT_BIT, it must also contain GL_MAP_PERSISTENT_BIT. + (access and GL_MAP_COHERENT_BIT == 0 || access and GL_MAP_PERSISTENT_BIT != 0) && + // If access contains GL_MAP_PERSISTENT_BIT, it must also contain at least one of GL_MAP_READ_BIT or GL_MAP_WRITE_BIT. + (access and GL_MAP_PERSISTENT_BIT == 0 || (access and (GL_MAP_READ_BIT or GL_MAP_WRITE_BIT) != 0)) /** * Returns whether the provided buffer target is bound From 892b0cd242c9d57376dccd0238dfd645df4ab120 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:11:01 -0400 Subject: [PATCH 44/45] fix: chunk update in render thread --- .../lambda/graphics/renderer/esp/ChunkedESP.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index d8ef909a1..0413ef180 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -119,17 +119,15 @@ class ChunkedESP private constructor( } suspend fun rebuild() { - awaitMainThread { - val newRenderer = StaticESPRenderer() + val newRenderer = awaitMainThread { StaticESPRenderer() } - iterateChunk { x, y, z -> - owner.update(newRenderer, chunk.world, x, y, z) - } + iterateChunk { x, y, z -> + owner.update(newRenderer, chunk.world, x, y, z) + } - owner.uploadQueue.add { - newRenderer.upload() - renderer = newRenderer - } + owner.uploadQueue.add { + newRenderer.upload() + renderer = newRenderer } } From b4f43673047572140cc009001d90a3b96fb9c853 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:15:38 -0400 Subject: [PATCH 45/45] removed unused library --- common/build.gradle.kts | 1 - fabric/build.gradle.kts | 1 - forge/build.gradle.kts | 1 - 3 files changed, 3 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index c954bde07..1c53b8e5f 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -44,7 +44,6 @@ dependencies { implementation("org.reflections:reflections:0.10.2") implementation("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") implementation("com.pngencoder:pngencoder:0.15.0") - implementation("org.bytedeco:ffmpeg-platform:6.1.1-1.5.10") // Add Kotlin implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 7288f95da..6901511b3 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -87,7 +87,6 @@ dependencies { includeLib("dev.babbaj:nether-pathfinder:1.5") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - includeLib("org.bytedeco:ffmpeg-platform:6.1.1-1.5.10") // Add mods to the mod jar includeMod("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index e0e454e31..34c16b7b8 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -98,7 +98,6 @@ dependencies { includeLib("org.javassist:javassist:3.28.0-GA") includeLib("com.github.Edouard127:KDiscordIPC:$discordIPCVersion") includeLib("com.pngencoder:pngencoder:0.15.0") - includeLib("org.bytedeco:ffmpeg-platform:6.1.1-1.5.10") // Add mods to the mod jar includeMod("thedarkcolour:kotlinforforge:$kotlinForgeVersion")