From 3828a49a1b4e9d4c439c2ccbdfa3d47c6503cfa3 Mon Sep 17 00:00:00 2001 From: "blade.kt" Date: Sat, 5 Apr 2025 02:00:33 +0300 Subject: [PATCH 1/6] Blur --- .../graphics/buffer/frame/FrameBuffer.kt | 28 +++-- .../buffer/vertex/attributes/VertexAttrib.kt | 4 +- .../com/lambda/graphics/gl/GlStateUtils.kt | 2 +- .../graphics/pipeline/VertexPipeline.kt | 2 +- .../renderer/gui/AbstractGUIRenderer.kt | 4 +- .../graphics/renderer/gui/BlurRenderer.kt | 114 ++++++++++++++++++ .../graphics/renderer/gui/FontRenderer.kt | 8 +- .../graphics/renderer/gui/RectRenderer.kt | 12 +- .../com/lambda/graphics/shader/Shader.kt | 13 +- .../com/lambda/gui/component/DockingRect.kt | 93 -------------- .../com/lambda/gui/component/window/Window.kt | 8 +- .../gui/component/window/WindowContent.kt | 18 ++- .../lambda/gui/impl/clickgui/ModuleWindow.kt | 12 ++ .../kotlin/com/lambda/module/HudModule.kt | 12 ++ .../lambda/module/modules/client/ClickGui.kt | 48 ++++++-- .../lambda/shaders/post/gaussian_h.glsl | 30 +++++ .../lambda/shaders/post/gaussian_v.glsl | 30 +++++ .../assets/lambda/shaders/renderer/font.glsl | 3 +- .../lambda/shaders/shared/gaussian.glsl | 31 +++++ 19 files changed, 331 insertions(+), 141 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt delete mode 100644 common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt create mode 100644 common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl create mode 100644 common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl create mode 100644 common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt index 468225cb7..f9e8c0ee4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt @@ -27,14 +27,14 @@ import org.lwjgl.opengl.GL30C.* import java.nio.IntBuffer open class FrameBuffer( - protected var width: Int = 1, - protected var height: Int = 1, + var width: Int = 1, + var height: Int = 1, private val depth: Boolean = false ) { - private val fbo = glGenFramebuffers() + val fbo = glGenFramebuffers() - private val colorAttachment = glGenTextures() - private val depthAttachment by lazy(::glGenTextures) + val colorAttachment = glGenTextures() + val depthAttachment by lazy(::glGenTextures) private val clearMask = if (!depth) GL_COLOR_BUFFER_BIT else GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT @@ -57,8 +57,18 @@ open class FrameBuffer( return this } - private fun update() { - if (width == lastWidth && height == lastWidth) { + fun bind() { + glBindFramebuffer(GL_FRAMEBUFFER, fbo) + } + + fun updateScreenSized() { + width = mc.framebuffer.viewportWidth + height = mc.framebuffer.viewportHeight + update() + } + + fun update() { + if (width == lastWidth && height == lastHeight) { glClear(clearMask) return } @@ -114,5 +124,9 @@ open class FrameBuffer( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) } + + fun unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, mc.framebuffer.fbo) + } } } \ No newline at end of file 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 bac651b5f..18e08a54e 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 @@ -77,8 +77,8 @@ sealed class VertexAttrib( Vec3, Vec2, Color ) - object RECT_OUTLINE : Group( - Vec3, Vec2, Float, Color + object BLUR : Group( + Vec2, Vec2 ) // WORLD diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt b/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt index ddcefd46e..df44bceeb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt @@ -88,7 +88,7 @@ object GlStateUtils { field.set(flag) } - private fun blend(flag: Boolean) { + fun blend(flag: Boolean) { if (flag) { glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt index 31c1c3bf5..43c5c00ea 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt @@ -39,7 +39,7 @@ class VertexPipeline( ) { private val vao = VertexArray(vertexMode, attributes) private val vbo = PersistentBuffer(GL_ARRAY_BUFFER, attributes.stride) - private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, 4) + private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, UInt.SIZE_BYTES) /** * Direct access to the vertex buffer's underlying byte storage diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt index 755031514..d75859adc 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt @@ -63,7 +63,9 @@ open class AbstractGUIRenderer( shader["u_ShadeColor1"] = primaryColor shader["u_ShadeColor2"] = secondaryColor - shader["u_ShadeSize"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) + var size = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) + //if (this is FontRenderer) size *= 5.0 + shader["u_ShadeSize"] = size } pipeline.apply { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt new file mode 100644 index 000000000..f3fe3ecbd --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2025 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.renderer.gui + +import com.lambda.Lambda.mc +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.buffer.frame.FrameBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.GlStateUtils +import com.lambda.graphics.pipeline.VertexPipeline +import com.lambda.graphics.shader.Shader.Companion.shader +import com.lambda.graphics.texture.TextureUtils.bindTexture +import com.lambda.util.math.Rect +import com.lambda.util.math.Vec2d +import org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER +import org.lwjgl.opengl.GL30C.glBindFramebuffer + +// ToDo: Improve blur +// Round Corners +// Boost brightness +// BlurRect layout impl +object BlurRenderer { + private val fbo1 = FrameBuffer() + private val fbo2 = FrameBuffer() + private val base get() = mc.framebuffer + + private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.BLUR) + + private val hShader = shader("post/gaussian_h") + private val vShader = shader("post/gaussian_v") + + fun blur(rect: Rect, iterations: Int) { + if (iterations <= 0) return + + vao.upload { + val p1 = rect.leftTop + val p2 = rect.rightBottom + + buildQuad( + vertex { + vec2(p1.x, p1.y).vec2(0.0, 0.0) + }, + vertex { + vec2(p1.x, p2.y).vec2(0.0, 1.0) + }, + vertex { + vec2(p2.x, p2.y).vec2(1.0, 1.0) + }, + vertex { + vec2(p2.x, p1.y).vec2(1.0, 0.0) + } + ) + } + + val i = iterations.toDouble() + + GlStateUtils.blend(false) + render(null, fbo1, false, i - 1.0, i) + + repeat(iterations - 1) { + render(fbo1, fbo2, true , i - it, i - it) + render(fbo2, fbo1, false, i-it-1, i - it) + } + + render(fbo1, null, true) + GlStateUtils.blend(true) + vao.end() + } + + init { + listen(alwaysListen = true) { + vao.sync() + } + } + + private fun render( + color: FrameBuffer?, + frameBuffer: FrameBuffer?, + vertical: Boolean, + extendX: Double = 0.0, + extendY: Double = 0.0 + ) { + color?.bindColorTexture() ?: bindTexture(base.colorAttachment) + + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer?.fbo ?: base.fbo) + frameBuffer?.updateScreenSized() + + (if (vertical) vShader else hShader).let { shader -> + shader.use() + shader["u_TexelSize"] = Vec2d(1.0 / base.viewportWidth, 1.0 / base.viewportHeight) + shader["u_Extend"] = Vec2d(extendX, extendY) + // if (vertical) shader["u_Final"] = frameBuffer == null + } + + vao.render() + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt index 53ee181d5..6d9378908 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt @@ -61,8 +61,9 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende color: Color = Color.WHITE, scale: Double = ClickGui.fontScale, shadow: Boolean = true, - parseEmoji: Boolean = LambdaMoji.isEnabled - ) = render { + parseEmoji: Boolean = LambdaMoji.isEnabled, + shade: Boolean = false + ) = render(shade) { shader["u_FontTexture"] = 0 shader["u_EmojiTexture"] = 1 shader["u_SDFMin"] = RenderSettings.sdfMin @@ -90,7 +91,8 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende position: Vec2d, color: Color = Color.WHITE, scale: Double = ClickGui.fontScale, - ) = render { + shade: Boolean = false + ) = render(shade) { shader["u_FontTexture"] = 0 shader["u_EmojiTexture"] = 1 shader["u_SDFMin"] = RenderSettings.sdfMin diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt index 1956d957d..efd965def 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt @@ -71,7 +71,7 @@ object RectRenderer { outline.putRect( rect, - width * 0.5, + width * 0.25, shade, leftTopRadius, rightTopRadius, @@ -154,7 +154,7 @@ object RectRenderer { val pos2 = rect.rightBottom val expand = expandIn.coerceAtLeast(0.0) + 1 - val smoothing = 0.5 + val smoothing = 0.3 val p1 = pos1 - expand - smoothing val p2 = pos2 + expand + smoothing @@ -192,16 +192,16 @@ object RectRenderer { upload { buildQuad( vertex { - vec3m(p1.x, p1.y, 0.0).vec2(uv1.x, uv1.y).color(leftTop) + vec3m(p1.x, p1.y).vec2(uv1.x, uv1.y).color(leftTop) }, vertex { - vec3m(p1.x, p2.y, 0.0).vec2(uv1.x, uv2.y).color(leftBottom) + vec3m(p1.x, p2.y).vec2(uv1.x, uv2.y).color(leftBottom) }, vertex { - vec3m(p2.x, p2.y, 0.0).vec2(uv2.x, uv2.y).color(rightBottom) + vec3m(p2.x, p2.y).vec2(uv2.x, uv2.y).color(rightBottom) }, vertex { - vec3m(p2.x, p1.y, 0.0).vec2(uv2.x, uv1.y).color(rightTop) + vec3m(p2.x, p1.y).vec2(uv2.x, uv1.y).color(rightTop) } ) } diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt index 8b42dfd93..3ebcb54bc 100644 --- a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt +++ b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt @@ -24,6 +24,7 @@ import com.lambda.graphics.shader.ShaderUtils.uniformMatrix import com.lambda.util.math.Vec2d import it.unimi.dsi.fastutil.objects.Object2IntMap import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.util.math.Vec3d import org.joml.Matrix4f import org.lwjgl.opengl.GL20C.* @@ -49,13 +50,9 @@ class Shader private constructor(name: String) { } private fun loc(name: String) = - if (uniformCache.containsKey(name)) - uniformCache.getInt(name) - else - glGetUniformLocation(id, name).let { location -> - uniformCache.put(name, location) - location - } + uniformCache.getOrPut(name) { + glGetUniformLocation(id, name) + } operator fun set(name: String, v: Boolean) = glUniform1i(loc(name), if (v) 1 else 0) @@ -85,7 +82,7 @@ class Shader private constructor(name: String) { uniformMatrix(loc(name), mat) companion object { - private val shaderCache = hashMapOf() + private val shaderCache = Object2ObjectOpenHashMap() fun shader(path: String) = shaderCache.getOrPut(path) { diff --git a/common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt b/common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt deleted file mode 100644 index 785f3e826..000000000 --- a/common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.gui.component - -import com.lambda.module.modules.client.ClickGui -import com.lambda.util.math.MathUtils.roundToStep -import com.lambda.util.math.Rect -import com.lambda.util.math.Vec2d -import com.lambda.util.math.coerceIn - -abstract class DockingRect { - abstract var relativePos: Vec2d - protected abstract val width: Double - protected abstract val height: Double - protected val size get() = Vec2d(width, height) - - val rect get() = Rect.basedOn(position, size) - open val dockingBase get() = rect.center - - open val autoDocking = false - open val allowHAlign = true - open val allowVAlign = true - - open var dockingH = HAlign.LEFT; set(to) { - val from = field - field = to - - val delta = to.multiplier - from.multiplier - relativePos += Vec2d.RIGHT * delta * (size.x - screenSize.x) - } - - open var dockingV = VAlign.TOP; set(to) { - val from = field - field = to - - val delta = to.multiplier - from.multiplier - relativePos += Vec2d.BOTTOM * delta * (size.y - screenSize.y) - } - - var screenSize: Vec2d = Vec2d.ZERO - - var position: Vec2d - get() = relativeToAbs(relativePos) - .coerceIn(0.0, screenSize.x - size.x, 0.0, screenSize.y - size.y) - set(value) { - relativePos = absToRelative(value.roundToStep(ClickGui.dockingGridSize)) - if (autoDocking) autoDocking() - } - - private val dockingOffset get() = - (screenSize - size) * Vec2d(dockingH.multiplier, dockingV.multiplier) - - private fun relativeToAbs(posIn: Vec2d) = posIn + dockingOffset - private fun absToRelative(posIn: Vec2d) = posIn - dockingOffset - - fun autoDocking() { - val screenCenterX = (screenSize.x * 0.3333)..(screenSize.x * 0.6666) - val screenCenterY = (screenSize.y * 0.3333)..(screenSize.y * 0.6666) - - val drawableCenter = dockingBase - - dockingH = if (allowHAlign) { - when { - drawableCenter.x < screenCenterX.start -> HAlign.LEFT - drawableCenter.x > screenCenterX.endInclusive -> HAlign.RIGHT - else -> HAlign.CENTER - } - } else HAlign.LEFT - - dockingV = if (allowVAlign) { - when { - drawableCenter.y < screenCenterY.start -> VAlign.TOP - drawableCenter.y > screenCenterY.endInclusive -> VAlign.BOTTOM - else -> VAlign.CENTER - } - } else VAlign.TOP - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt b/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt index 791de159c..5733771fd 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt @@ -165,7 +165,7 @@ open class Window( init { position = initialPosition - properties.clampPosition = owner is RootLayout + //properties.clampPosition = owner is RootLayout onUpdate { // Update it here @@ -275,7 +275,7 @@ open class Window( /** * [Disabled] -> No ability to minimize the window * [Relative] -> Animation follows the height of the component ( animation(0.0, height) ) (height change is animated) - * [Absolute] -> Animation does not depend on the height ( animation(0.0, 1.0) * height ) (height change instantly affects the height) + * [Absolute] -> Animation does not depend on the height ( animation(0.0, 1.0) * height ) (height change instantly affects the output height) */ enum class Minimizing { Disabled, @@ -316,14 +316,14 @@ open class Window( minimizing: Minimizing = Minimizing.Relative, resizable: Boolean = true, autoResize: AutoResize = AutoResize.Disabled, - block: WindowContent.() -> Unit = {} + block: WindowContent.(Window) -> Unit = {} ) = Window( this, title, position, size, draggable, scrollable, minimizing, resizable, autoResize ).apply(children::add).apply { - block(this.content) + block(this.content, this) } private const val RESIZE_RANGE = 5.0 diff --git a/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt b/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt index ad997f854..7c89d7895 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt @@ -29,7 +29,7 @@ import kotlin.math.abs class WindowContent( owner: Window, - scrollable: Boolean + var scrollable: Boolean ) : Layout(owner) { private val window = owner private val animation = animationTicker(false) @@ -40,6 +40,8 @@ class WindowContent( private var renderScrollOffset by animation.exp(0.7) { scrollOffset + rubberbandDelta } + var freeScroll = false + override val scissorRect: Rect get() = Rect(window.titleBar.leftBottom, window.rightBottom) @@ -93,9 +95,11 @@ class WindowContent( dwheel = 0.0 val prevOffset = scrollOffset - scrollOffset = scrollOffset.coerceAtLeast( - owner.targetHeight - height - ).coerceAtMost(0.0) + if (!freeScroll) { + scrollOffset = scrollOffset.coerceAtLeast( + owner.targetHeight - height + ).coerceAtMost(0.0) + } rubberbandDelta += prevOffset - scrollOffset rubberbandDelta *= 0.5 @@ -104,8 +108,10 @@ class WindowContent( animation.tick() } - onMouseScroll { delta -> - dwheel += delta * 10.0 + owner.onMouseScroll { delta -> + if (owner.autoResize.enabled) return@onMouseScroll + if (!scrollable) return@onMouseScroll + dwheel += delta * 15.0 } } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt index 8d204562c..4c440aea1 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt @@ -17,6 +17,7 @@ package com.lambda.gui.impl.clickgui +import com.lambda.graphics.renderer.gui.BlurRenderer import com.lambda.module.tag.ModuleTag import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout @@ -25,6 +26,7 @@ import com.lambda.gui.component.window.WindowContent import com.lambda.gui.impl.clickgui.module.ModuleLayout.Companion.backgroundTint import com.lambda.gui.impl.clickgui.module.ModuleLayout.Companion.moduleLayout import com.lambda.module.ModuleRegistry +import com.lambda.module.modules.client.ClickGui import com.lambda.util.math.Vec2d class ModuleWindow( @@ -49,6 +51,16 @@ class ModuleWindow( onWindowExpand { minimize() } onWindowMinimize { minimize() } + + layoutBehind(titleBar) { + onUpdate { + rect = this@ModuleWindow.rect + } + + onRender { + BlurRenderer.blur(rect, ClickGui.blurStrength) + } + } } companion object { diff --git a/common/src/main/kotlin/com/lambda/module/HudModule.kt b/common/src/main/kotlin/com/lambda/module/HudModule.kt index b22ee6c95..711eac25a 100644 --- a/common/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/common/src/main/kotlin/com/lambda/module/HudModule.kt @@ -22,6 +22,7 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.animation.AnimationTicker +import com.lambda.graphics.renderer.gui.BlurRenderer import com.lambda.gui.component.core.FilledRect.Companion.rect import com.lambda.gui.component.core.GlowRect.Companion.glow import com.lambda.gui.component.core.OutlineRect.Companion.outline @@ -53,6 +54,17 @@ abstract class HudModule( Layout(null).apply { if (!background) return@apply + layout { + onUpdate { + rect = this@apply.rect + } + + onRender { + if (!ClickGui.hudBlur) return@onRender + BlurRenderer.blur(rect, ClickGui.blurStrength) + } + } + rect { onUpdate { position = this@apply.position diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt b/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt index 3c7ccacbe..f9fab5b81 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt @@ -18,6 +18,7 @@ package com.lambda.module.modules.client import com.lambda.Lambda.mc +import com.lambda.graphics.RenderMain import com.lambda.gui.LambdaScreen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -27,6 +28,8 @@ import com.lambda.gui.component.VAlign import com.lambda.gui.component.core.FilledRect.Companion.rect import com.lambda.gui.component.core.GlowRect.Companion.glow import com.lambda.gui.component.layout.Layout +import com.lambda.gui.component.window.Window +import com.lambda.gui.component.window.Window.Companion.window import com.lambda.gui.impl.clickgui.ModuleWindow.Companion.moduleWindow import com.lambda.gui.impl.clickgui.core.AnimatedChild.Companion.animatedBackground import com.lambda.gui.impl.clickgui.module.setting.settings.UnitButton.Companion.unitButton @@ -49,21 +52,24 @@ object ClickGui : Module( val settingsHeight by setting("Settings Height", 16.0, 10.0..25.0, 0.1) val padding by setting("Padding", 1.0, 1.0..6.0, 0.1) val listStep by setting("List Step", 1.0, 0.0..6.0, 0.1) - val autoResize by setting("Auto Resize", false) + val autoResize by setting("Auto Resize", true) val roundRadius by setting("Round Radius", 3.0, 0.0..10.0, 0.1) + val blurStrength by setting("Blur Strength", 2, 0..10, 1) + val hudBlur by setting("HUD Blur", true) { blurStrength > 0 } + val backgroundTint by setting("Background Tint", Color.BLACK.setAlpha(0.4)) val backgroundCornerTint by setting("Background Corner Tint", Color.WHITE.setAlpha(0.4)) - val cornerTintWidth by setting("Corner Tint Width", 100.0, 1.0..500.0, 0.1) + val cornerTintWidth by setting("Corner Tint Width", 1.0, 1.0..500.0, 0.1) val cornerTintShade by setting("Corner Tint Shade", true) - val titleBackgroundColor by setting("Title Background Color", Color(80, 80, 80)) + val titleBackgroundColor by setting("Title Background Color", Color(80, 80, 80, 180)) val backgroundColor by setting("Background Color", titleBackgroundColor) val backgroundShade by setting("Background Shade", true) val outline by setting("Outline", true) - val outlineWidth by setting("Outline Width", 0.5, 0.5..5.0, 0.1) { outline } + val outlineWidth by setting("Outline Width", 1.0, 0.5..5.0, 0.1) { outline } val outlineColor by setting("Outline Color", Color.WHITE) { outline } val outlineShade by setting("Outline Shade", true) { outline } @@ -74,9 +80,8 @@ object ClickGui : Module( val fontScale by setting("Font Scale", 1.0, 0.5..2.0, 0.1) val fontOffset by setting("Font Offset", 4.0, 0.0..5.0, 0.1) - val dockingGridSize by setting("Docking Grid Size", 1.0, 0.1..10.0, 0.1) - val moduleEnabledColor by setting("Module Enabled Color", Color.WHITE.setAlpha(0.4)) + val moduleEnabledColor by setting("Module Enabled Color", Color.WHITE.setAlpha(0.6)) val moduleDisabledColor by setting("Module Disabled Color", Color.WHITE.setAlpha(0.0)) val moduleHoverAccent by setting("Module Hover Accent", 0.15, 0.0..0.3, 0.01) val moduleOpenAccent by setting("Module Open Accent", 0.3, 0.0..0.5, 0.01) @@ -115,8 +120,35 @@ object ClickGui : Module( var x = 10.0 val y = x - ModuleTag.defaults.forEach { tag -> - x += moduleWindow(tag, Vec2d(x, y)).width + 5 + window( // wrap module windows within a scrollable window + draggable = false, + minimizing = Window.Minimizing.Disabled + ) { window -> + window.use { + titleBar.onUpdate { + height = 0.0 + } + + onUpdate { + position = Vec2d.ZERO + size = RenderMain.screenSize + + content.freeScroll = true + content.scrollable = ClickGui.autoResize + } + + listOf( + titleBar.textField, + titleBarBackground, + contentBackground, + outlineRect, + glowRect + ).forEach(Layout::destroy) + } + + ModuleTag.defaults.forEach { tag -> + x += moduleWindow(tag, Vec2d(x, y)).width + 5 + } } switchButton("HUD", ::HUD) diff --git a/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl b/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl new file mode 100644 index 000000000..f5a68cd83 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl @@ -0,0 +1,30 @@ +attributes { + vec2 pos; + vec2 uv; +}; + +#include "gaussian" + +void fragment() { + vec4 sum = texture(u_Texture, v_TexCoord) * WEIGHT_BASE; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_0 * u_TexelSize.x, 0.0)) * WEIGHT_0; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_0 * u_TexelSize.x, 0.0)) * WEIGHT_0; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_1 * u_TexelSize.x, 0.0)) * WEIGHT_1; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_1 * u_TexelSize.x, 0.0)) * WEIGHT_1; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_2 * u_TexelSize.x, 0.0)) * WEIGHT_2; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_2 * u_TexelSize.x, 0.0)) * WEIGHT_2; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_3 * u_TexelSize.x, 0.0)) * WEIGHT_3; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_3 * u_TexelSize.x, 0.0)) * WEIGHT_3; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_4 * u_TexelSize.x, 0.0)) * WEIGHT_4; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_4 * u_TexelSize.x, 0.0)) * WEIGHT_4; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_5 * u_TexelSize.x, 0.0)) * WEIGHT_5; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_5 * u_TexelSize.x, 0.0)) * WEIGHT_5; + + color = sum; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl b/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl new file mode 100644 index 000000000..8c498b087 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl @@ -0,0 +1,30 @@ +attributes { + vec2 pos; + vec2 uv; +}; + +#include "gaussian" + +void fragment() { + vec4 sum = texture(u_Texture, v_TexCoord) * WEIGHT_BASE; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_0 * u_TexelSize.y)) * WEIGHT_0; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_0 * u_TexelSize.y)) * WEIGHT_0; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_1 * u_TexelSize.y)) * WEIGHT_1; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_1 * u_TexelSize.y)) * WEIGHT_1; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_2 * u_TexelSize.y)) * WEIGHT_2; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_2 * u_TexelSize.y)) * WEIGHT_2; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_3 * u_TexelSize.y)) * WEIGHT_3; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_3 * u_TexelSize.y)) * WEIGHT_3; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_4 * u_TexelSize.y)) * WEIGHT_4; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_4 * u_TexelSize.y)) * WEIGHT_4; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_5 * u_TexelSize.y)) * WEIGHT_5; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_5 * u_TexelSize.y)) * WEIGHT_5; + + color = sum; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl index 5d4abd1dc..6dbf076e2 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl @@ -17,6 +17,7 @@ export { }; #include "sdf" +#include "shade" void fragment() { bool isEmoji = v_TexCoord.x < 0.0; @@ -28,5 +29,5 @@ void fragment() { } float sdf = sdf(texture(u_FontTexture, v_TexCoord).r, u_SDFMin, u_SDFMax); - color = vec4(1.0, 1.0, 1.0, sdf) * v_Color; + color = vec4(1.0, 1.0, 1.0, sdf) * shade * v_Color; }# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl b/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl new file mode 100644 index 000000000..3a13c96c3 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl @@ -0,0 +1,31 @@ +attributes { + vec2 pos; + vec2 uv; +}; + +uniforms { + sampler2D u_Texture; # fragment + vec2 u_TexelSize; # global + vec2 u_Extend; # vertex +}; + +export { + core gl_Position; # u_ProjModel * vec4(pos, 0.0, 1.0) + vec4(u_TexelSize * u_Extend * vec2(uv.x - 0.5, 0.5 - uv.y) * 2 * 12, 0.0, 0.0) + vec2 v_TexCoord; # gl_Position.xy * 0.5 + 0.5 +}; + +#define WEIGHT_BASE 0.09950225481157 + +#define WEIGHT_0 0.18446050802858 +#define WEIGHT_1 0.13614942259252 +#define WEIGHT_2 0.07862968075756 +#define WEIGHT_3 0.03538335634090 +#define WEIGHT_4 0.01232869558916 +#define WEIGHT_5 0.00329720928547 + +#define OFFSET_0 1.47692307692307 +#define OFFSET_1 3.44615384615384 +#define OFFSET_2 5.41538461538461 +#define OFFSET_3 7.38461538461538 +#define OFFSET_4 9.35384615384615 +#define OFFSET_5 11.3230769230769 \ No newline at end of file From 1cf79c0355fe3e999dd93e36c222bb44dada0823 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Fri, 4 Apr 2025 19:29:58 -0400 Subject: [PATCH 2/6] Buffer bind block --- .../com/lambda/graphics/buffer/Buffer.kt | 16 +++++++++------ .../graphics/buffer/pixel/PixelBuffer.kt | 2 -- .../graphics/buffer/vertex/VertexArray.kt | 20 +++++++++---------- .../graphics/pipeline/PersistentBuffer.kt | 6 +----- .../graphics/pipeline/VertexPipeline.kt | 4 +--- 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt index 28f7b4872..2eb410e82 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt @@ -113,19 +113,23 @@ abstract class Buffer( private val bufferIds = IntArray(buffers) /** - * Binds the buffer id to the [target]. + * Execute the [block] in a bound context */ - open fun bind(id: Int) = glBindBuffer(target, id) + fun bind(block: Buffer.() -> Unit) { + bind() + block(this) + bind(0) + } /** - * Binds current the buffer [index] to the [target]. + * Binds the buffer id to the [target]. */ - fun bind() = bind(bufferAt(index)) + open fun bind(id: Int) = glBindBuffer(target, id) /** - * Returns the id of the buffer based on the index. + * Binds current the buffer [index] to the [target]. */ - fun bufferAt(index: Int) = bufferIds[index] + fun bind() = bind(bufferIds[index]) /** * Swaps the buffer [index] if [buffers] is greater than 1. 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 179f9477e..02a6187f5 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 @@ -18,9 +18,7 @@ package com.lambda.graphics.buffer.pixel import com.lambda.graphics.buffer.Buffer -import com.lambda.graphics.gl.putTo import com.lambda.graphics.texture.Texture -import com.lambda.threading.runSafeGameScheduled import com.lambda.util.math.MathUtils.toInt import org.lwjgl.opengl.GL45C.* import java.nio.ByteBuffer 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 f494bdab6..4e68a9157 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 @@ -37,17 +37,15 @@ class VertexArray( indicesSize: Long, indicesPointer: Long, verticesOffset: Int - ) { - bind() - glDrawElementsBaseVertex( - vertexMode.mode, - indicesSize.toInt() / UInt.SIZE_BYTES, - GL_UNSIGNED_INT, - indicesPointer, - verticesOffset / attributes.stride - ) - bind(0) - } + ) = bind { + glDrawElementsBaseVertex( + vertexMode.mode, + indicesSize.toInt() / UInt.SIZE_BYTES, + GL_UNSIGNED_INT, + indicesPointer, + verticesOffset / attributes.stride + ) + } override fun map(size: Long, offset: Long, block: (ByteBuffer) -> Unit) = throw UnsupportedOperationException() override fun upload(data: ByteBuffer, offset: Long) = throw UnsupportedOperationException() diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt index ee8d3fbfc..a91cccbca 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt @@ -95,11 +95,7 @@ class PersistentBuffer( cacheSize = 0 } - fun use(block: () -> Unit) { - glBuffer.bind() - block() - glBuffer.bind(0) - } + fun use(block: () -> Unit) = glBuffer.bind { block() } private fun memcmp(a: ByteBuffer, b: ByteBuffer, pointer: Int, size: Int): Boolean { for (i in pointer..<(pointer + size)) { diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt index 43c5c00ea..19410baeb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt @@ -145,8 +145,6 @@ class VertexPipeline( } init { - vao.bind() - vbo.use(attributes::link) - vao.bind(0) + vao.bind { vbo.use(attributes::link) } } } From 687dec0357c24c8a7491f610ddedf7782e3e8182 Mon Sep 17 00:00:00 2001 From: "blade.kt" Date: Tue, 8 Apr 2025 02:36:21 +0300 Subject: [PATCH 3/6] Optimizatio --- .../kotlin/com/lambda/graphics/RenderMain.kt | 1 - .../com/lambda/graphics/buffer/Buffer.kt | 21 +- .../graphics/buffer/vertex/VertexArray.kt | 30 +- .../buffer/vertex/attributes/VertexAttrib.kt | 29 +- .../graphics/pipeline/PersistentBuffer.kt | 45 +-- .../lambda/graphics/pipeline/VertexBuilder.kt | 22 +- .../graphics/pipeline/VertexPipeline.kt | 27 +- .../renderer/gui/AbstractGUIRenderer.kt | 76 ----- .../graphics/renderer/gui/BlurRenderer.kt | 46 +-- .../graphics/renderer/gui/FontRenderer.kt | 262 ++++-------------- .../graphics/renderer/gui/RectRenderer.kt | 83 +++--- .../renderer/gui/font/CachedString.kt | 186 +++++++++++++ .../com/lambda/graphics/shader/Shader.kt | 20 ++ .../lambda/gui/component/core/TextField.kt | 3 +- .../com/lambda/gui/component/layout/Layout.kt | 41 +-- .../module/modules/client/LambdaMoji.kt | 18 +- .../module/modules/client/RenderSettings.kt | 39 ++- .../module/modules/combat/CrystalAura.kt | 6 +- .../lambda/module/modules/render/BlockESP.kt | 5 +- .../lambda/shaders/post/gaussian_h.glsl | 3 +- .../lambda/shaders/post/gaussian_v.glsl | 3 +- .../assets/lambda/shaders/renderer/font.glsl | 16 +- .../lambda/shaders/renderer/rect_filled.glsl | 6 +- .../lambda/shaders/renderer/rect_glow.glsl | 6 +- .../lambda/shaders/renderer/rect_outline.glsl | 6 +- .../lambda/shaders/shared/gaussian.glsl | 8 +- .../assets/lambda/shaders/shared/rect.glsl | 20 +- 27 files changed, 534 insertions(+), 494 deletions(-) delete mode 100644 common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt diff --git a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt index 9681c93af..91c4dfb72 100644 --- a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -28,7 +28,6 @@ import com.lambda.graphics.gl.Matrices.resetMatrices import com.lambda.graphics.renderer.esp.global.DynamicESP import com.lambda.graphics.renderer.esp.global.StaticESP import com.lambda.module.modules.client.GuiSettings -import com.lambda.util.Communication.info import com.lambda.util.math.Vec2d import com.mojang.blaze3d.systems.RenderSystem.getProjectionMatrix import org.joml.Matrix4f diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt index 2eb410e82..0b48e97d4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt @@ -36,6 +36,7 @@ abstract class Buffer( * Edge case to handle vertex arrays */ val isVertexArray: Boolean = false, + val validate: Boolean = true ) { /** * Specifies how the buffers are used @@ -105,7 +106,19 @@ abstract class Buffer( /** * Index of the current buffer. */ - var index: Int = 0; private set + private var index: Int = 0; private set(value) { + if (field == value) return + field = value + id = bufferIds[value] + } + + /** + * ID of the current buffer. + */ + var id: Int = 0; get() { + if (field == 0) field = bufferIds[0] + return field + } private set /** * List of all the buffers. @@ -129,7 +142,7 @@ abstract class Buffer( /** * Binds current the buffer [index] to the [target]. */ - fun bind() = bind(bufferIds[index]) + fun bind() = bind(id) /** * Swaps the buffer [index] if [buffers] is greater than 1. @@ -321,6 +334,8 @@ abstract class Buffer( abstract fun upload(data: ByteBuffer, offset: Long) private fun validate() { + if (!validate) return + check(usage in GL_STREAM_DRAW..GL_DYNAMIC_COPY) { "Usage is invalid, refer to the documentation table." } @@ -363,7 +378,7 @@ abstract class Buffer( var lastIbo = 0 var prevIbo = 0 - fun createPipelineBuffer(bufferTarget: Int) = object : Buffer(buffers = 1) { + fun createPipelineBuffer(bufferTarget: Int) = object : Buffer(buffers = 1, validate = false) { override val target: Int = bufferTarget override val usage: Int = GL_STATIC_DRAW 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 4e68a9157..ca748e01a 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 @@ -20,20 +20,33 @@ package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.Buffer import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.pipeline.PersistentBuffer import net.minecraft.client.render.BufferRenderer import org.lwjgl.opengl.GL30C.* import org.lwjgl.opengl.GL32C.glDrawElementsBaseVertex import java.nio.ByteBuffer class VertexArray( - private val vertexMode: VertexMode, - private val attributes: VertexAttrib.Group + val vertexMode: VertexMode, + val attributes: VertexAttrib.Group ) : Buffer(isVertexArray = true) { override val usage: Int = -1 override val target: Int = -1 override val access: Int = -1 - fun render( + private var linkedVBO: PersistentBuffer? = null + + fun renderIndices( + ibo: PersistentBuffer + ) = linkedVBO?.let { vbo -> + renderInternal( + indicesSize = ibo.byteBuffer.bytesPut - ibo.uploadOffset, + indicesPointer = ibo.byteBuffer.pointer + ibo.uploadOffset, + verticesOffset = vbo.uploadOffset + ) + } ?: throw IllegalStateException("Unable to use vertex array without having a VBO linked to it.") + + private fun renderInternal( indicesSize: Long, indicesPointer: Long, verticesOffset: Int @@ -47,6 +60,17 @@ class VertexArray( ) } + fun linkVbo(vbo: PersistentBuffer, block: VertexArray.() -> Unit = { }) { + linkedVBO = vbo + + bind { + vbo.use { + attributes.link() + block(this@VertexArray) + } + } + } + override fun map(size: Long, offset: Long, block: (ByteBuffer) -> Unit) = throw UnsupportedOperationException() override fun upload(data: ByteBuffer, offset: Long) = throw UnsupportedOperationException() 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 18e08a54e..dabbfbf68 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 @@ -21,36 +21,34 @@ import org.lwjgl.opengl.GL11C.GL_FLOAT import org.lwjgl.opengl.GL11C.GL_UNSIGNED_BYTE import org.lwjgl.opengl.GL20C.glEnableVertexAttribArray import org.lwjgl.opengl.GL20C.glVertexAttribPointer -import org.lwjgl.opengl.GL33.glVertexAttribDivisor sealed class VertexAttrib( private val componentCount: Int, componentSize: Int, private val normalized: Boolean, - private val single: Boolean, private val type: Int ) { open class Float( - normalized: Boolean = false, single: Boolean = false - ) : VertexAttrib(1, 4, normalized, single, GL_FLOAT) { + normalized: Boolean = false + ) : VertexAttrib(1, 4, normalized, GL_FLOAT) { companion object : Float() } open class Vec2( - normalized: Boolean = false, single: Boolean = false - ) : VertexAttrib(2, 4, normalized, single, GL_FLOAT) { + normalized: Boolean = false + ) : VertexAttrib(2, 4, normalized, GL_FLOAT) { companion object : Vec2() } open class Vec3( - normalized: Boolean = false, single: Boolean = false - ) : VertexAttrib(3, 4, normalized, single, GL_FLOAT) { + normalized: Boolean = false + ) : VertexAttrib(3, 4, normalized, GL_FLOAT) { companion object : Vec3() } open class Color( - normalized: Boolean = true, single: Boolean = false - ) : VertexAttrib(4, 1, normalized, single, GL_UNSIGNED_BYTE) { + normalized: Boolean = true + ) : VertexAttrib(4, 1, normalized, GL_UNSIGNED_BYTE) { companion object : Color() } @@ -59,7 +57,6 @@ sealed class VertexAttrib( fun link(index: Int, pointer: Long, stride: Int) { glEnableVertexAttribArray(index) glVertexAttribPointer(index, componentCount, type, normalized, stride, pointer) - if (single) glVertexAttribDivisor(index, 1) } @Suppress("ClassName") @@ -70,15 +67,7 @@ sealed class VertexAttrib( // GUI object FONT : Group( - Vec3, Vec2, Color - ) - - object RECT : Group( - Vec3, Vec2, Color - ) - - object BLUR : Group( - Vec2, Vec2 + Vec2, Vec2, Color ) // WORLD diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt index a91cccbca..76c2aa471 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt @@ -18,32 +18,33 @@ package com.lambda.graphics.pipeline import com.lambda.graphics.buffer.Buffer.Companion.createPipelineBuffer +import com.lambda.graphics.buffer.DynamicByteBuffer import com.lambda.graphics.buffer.DynamicByteBuffer.Companion.dynamicByteBuffer import com.lambda.graphics.gl.kibibyte import org.lwjgl.system.MemoryUtil.memCopy -import java.nio.ByteBuffer +import sun.misc.Unsafe /** * Represents a persistent dynamic coherent buffer for fast opengl rendering purposes */ class PersistentBuffer( - target: Int, stride: Int + target: Int, stride: Int, initialSize: Int = 1.kibibyte ) { /** * Resizable byte buffer that stores all data used last frame */ - val byteBuffer = dynamicByteBuffer(stride * 1.kibibyte) + val byteBuffer = dynamicByteBuffer(stride * initialSize) /** * Data that has passed through the buffer within previous frame */ private val snapshot = dynamicByteBuffer(1) - private var cacheSize = 0 + private var snapshotData = 0L /** * Represents a gpu-side buffer */ - private val glBuffer = createPipelineBuffer(target) + val glBuffer = createPipelineBuffer(target) private var glSize = 0 var uploadOffset = 0 @@ -58,19 +59,12 @@ class PersistentBuffer( glBuffer.allocate(byteBuffer.data) snapshot.realloc(byteBuffer.capacity) - cacheSize = 0 + snapshotData = 0 return + } - /* TODO: - Cache data in range min(snapshot.capacity, byteBuffer.bytesPut) - and force upload after byteBuffer.bytesPut - */ - } else if (cacheSize > 0 && snapshot.capacity >= byteBuffer.bytesPut) { - // TODO: precise compare-mapping to minimize uploaded data - // Split data by chunks of modified regions and upload them only - // Might be useful in cases when position updates but uv/color/etc doesn't - // Might be not... - if (memcmp(snapshot.data, byteBuffer.data, uploadOffset, dataCount.toInt())) return + if (snapshotData > 0 && snapshot.capacity >= byteBuffer.bytesPut) { + if (memcmp(snapshot, byteBuffer, uploadOffset, dataCount.toInt())) return } glBuffer.update(uploadOffset.toLong(), dataCount, dataStart) @@ -82,7 +76,7 @@ class PersistentBuffer( fun sync() { memCopy(byteBuffer.pointer, snapshot.pointer, byteBuffer.bytesPut) - cacheSize = byteBuffer.bytesPut.toInt() + snapshotData = byteBuffer.bytesPut byteBuffer.resetPosition() uploadOffset = 0 @@ -92,17 +86,26 @@ class PersistentBuffer( snapshot.resetPosition() byteBuffer.resetPosition() uploadOffset = 0 - cacheSize = 0 + snapshotData = 0 } fun use(block: () -> Unit) = glBuffer.bind { block() } - private fun memcmp(a: ByteBuffer, b: ByteBuffer, pointer: Int, size: Int): Boolean { - for (i in pointer..<(pointer + size)) { - if (a[i] != b[i]) { + private fun memcmp(a: DynamicByteBuffer, b: DynamicByteBuffer, position: Int, size: Int): Boolean { + for (i in position..<(position + size)) { + if (UNSAFE.getByte(null, a.pointer + i) != + UNSAFE.getByte(null, b.pointer + i)) { return false } } return true } + + companion object { + private val UNSAFE = run { + val unsafeField = Unsafe::class.java.getDeclaredField("theUnsafe") + unsafeField.setAccessible(true) + unsafeField.get(null) as Unsafe + } + } } diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt index aec8b8c12..a882f88d1 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt @@ -29,8 +29,8 @@ import org.joml.Vector4d class VertexBuilder( private val direct: VertexPipeline? = null ) { - private val vertices by lazy { mutableListOf() } - private val indices by lazy { mutableListOf() } + val vertices by lazy { mutableListOf() } + val indices by lazy { mutableListOf() } private var verticesCounter = 0 @@ -97,9 +97,8 @@ class VertexBuilder( fun collect(vararg indices: Int) = indices - fun use(block: VertexBuilder.() -> Unit) { + fun use(block: VertexBuilder.() -> Unit) = apply(block) - } /** * Creates a new vertex with specified attributes @@ -120,14 +119,19 @@ class VertexBuilder( "Builder is already associated with a rendering pipeline. Cannot upload data again." } - /* Upload vertices */ + uploadVertices(pipeline.vertices) + uploadIndices(pipeline.indices) + } + + fun uploadVertices(buffer: DynamicByteBuffer) { vertices.forEach { attribute -> - attribute.upload(pipeline.vertices) + attribute.upload(buffer) } + } - /* Upload indices */ + fun uploadIndices(buffer: DynamicByteBuffer) { indices.forEach { - pipeline.indices.putInt(it) + buffer.putInt(it) } } @@ -264,4 +268,4 @@ class VertexBuilder( var color: java.awt.Color ) : Attribute({ putColor(color) }) } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt index 19410baeb..8347580dd 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt @@ -19,6 +19,7 @@ package com.lambda.graphics.pipeline import com.lambda.graphics.buffer.vertex.VertexArray import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib.Vec2 import com.lambda.graphics.buffer.vertex.attributes.VertexMode import org.lwjgl.opengl.GL32C.* @@ -38,9 +39,14 @@ class VertexPipeline( private val attributes: VertexAttrib.Group ) { private val vao = VertexArray(vertexMode, attributes) + private val vbo = PersistentBuffer(GL_ARRAY_BUFFER, attributes.stride) private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, UInt.SIZE_BYTES) + init { + vao.linkVbo(vbo) + } + /** * Direct access to the vertex buffer's underlying byte storage */ @@ -55,11 +61,7 @@ class VertexPipeline( * Submits a draw call to the GPU using currently uploaded data * Binds VAO and issues glDrawElementsBaseVertex command */ - fun render() = vao.render( - indicesSize = indices.bytesPut - ibo.uploadOffset, - indicesPointer = indices.pointer + ibo.uploadOffset, - verticesOffset = vbo.uploadOffset - ) + fun render() = vao.renderIndices(ibo) /** * Builds and renders data constructed by [VertexBuilder] @@ -144,7 +146,18 @@ class VertexPipeline( ibo.clear() } - init { - vao.bind { vbo.use(attributes::link) } + companion object { + private val UI_RECT = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group(Vec2)).apply { + upload { + buildQuad( + vertex { vec2(0.0, 0.0) }, + vertex { vec2(0.0, 1.0) }, + vertex { vec2(1.0, 1.0) }, + vertex { vec2(1.0, 0.0) } + ) + } + } + + fun renderStaticRect() = UI_RECT.render() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt deleted file mode 100644 index d75859adc..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2025 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.renderer.gui - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.RenderMain -import com.lambda.graphics.pipeline.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.module.modules.client.GuiSettings.primaryColor -import com.lambda.module.modules.client.GuiSettings.secondaryColor -import com.lambda.module.modules.client.RenderSettings -import com.lambda.util.math.MathUtils.toInt -import com.lambda.util.math.Vec2d -import org.lwjgl.glfw.GLFW - -open class AbstractGUIRenderer( - attribGroup: VertexAttrib.Group, - val shader: Shader -) { - private val pipeline = VertexPipeline(VertexMode.TRIANGLES, attribGroup) - private var memoryMapping = true - - init { - listen(alwaysListen = true) { - memoryMapping = RenderSettings.useMemoryMapping - } - - listen(alwaysListen = true) { - if (memoryMapping) pipeline.sync() - } - } - - fun render( - shade: Boolean = false, - block: VertexPipeline.(Shader) -> Unit - ) { - shader.use() - - block(pipeline, shader) - - shader["u_Shade"] = shade.toInt().toDouble() - if (shade) { - shader["u_ShadeTime"] = GLFW.glfwGetTime() * GuiSettings.colorSpeed * 5.0 - shader["u_ShadeColor1"] = primaryColor - shader["u_ShadeColor2"] = secondaryColor - - var size = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) - //if (this is FontRenderer) size *= 5.0 - shader["u_ShadeSize"] = size - } - - pipeline.apply { - render() - if (memoryMapping) end() else clear() - } - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt index f3fe3ecbd..fd831a9ca 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt @@ -41,35 +41,25 @@ object BlurRenderer { private val fbo2 = FrameBuffer() private val base get() = mc.framebuffer - private val vao = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.BLUR) - private val hShader = shader("post/gaussian_h") private val vShader = shader("post/gaussian_v") fun blur(rect: Rect, iterations: Int) { if (iterations <= 0) return - vao.upload { - val p1 = rect.leftTop - val p2 = rect.rightBottom + val i = iterations.toDouble() + val texelSize = Vec2d(1.0 / base.viewportWidth, 1.0 / base.viewportHeight) + val (pos1, pos2) = rect.leftTop to rect.rightBottom - buildQuad( - vertex { - vec2(p1.x, p1.y).vec2(0.0, 0.0) - }, - vertex { - vec2(p1.x, p2.y).vec2(0.0, 1.0) - }, - vertex { - vec2(p2.x, p2.y).vec2(1.0, 1.0) - }, - vertex { - vec2(p2.x, p1.y).vec2(1.0, 0.0) - } - ) - } + hShader.use() + hShader["u_TexelSize"] = texelSize + hShader["u_Position1"] = pos1 + hShader["u_Position2"] = pos2 - val i = iterations.toDouble() + vShader.use() + vShader["u_TexelSize"] = texelSize + vShader["u_Position1"] = pos1 + vShader["u_Position2"] = pos2 GlStateUtils.blend(false) render(null, fbo1, false, i - 1.0, i) @@ -81,13 +71,6 @@ object BlurRenderer { render(fbo1, null, true) GlStateUtils.blend(true) - vao.end() - } - - init { - listen(alwaysListen = true) { - vao.sync() - } } private fun render( @@ -104,11 +87,10 @@ object BlurRenderer { (if (vertical) vShader else hShader).let { shader -> shader.use() - shader["u_TexelSize"] = Vec2d(1.0 / base.viewportWidth, 1.0 / base.viewportHeight) shader["u_Extend"] = Vec2d(extendX, extendY) - // if (vertical) shader["u_Final"] = frameBuffer == null + // if (vertical) shader["u_IsFinal"] = frameBuffer == null } - vao.render() + VertexPipeline.renderStaticRect() } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt index 6d9378908..7a63cc715 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt @@ -17,33 +17,49 @@ package com.lambda.graphics.renderer.gui +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.buffer.vertex.VertexArray import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.pipeline.VertexBuilder -import com.lambda.graphics.renderer.gui.font.core.GlyphInfo -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.Matrices +import com.lambda.graphics.renderer.gui.font.CachedString +import com.lambda.graphics.renderer.gui.font.CachedString.Companion.SCALE_FACTOR +import com.lambda.graphics.renderer.gui.font.CachedString.Companion.getStringWidth import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height +import com.lambda.graphics.shader.Shader.Companion.shadeUniforms import com.lambda.graphics.shader.Shader.Companion.shader import com.lambda.graphics.texture.TextureOwner.bind import com.lambda.module.modules.client.ClickGui -import com.lambda.module.modules.client.LambdaMoji import com.lambda.module.modules.client.RenderSettings -import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d -import com.lambda.util.math.a -import com.lambda.util.math.setAlpha +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import org.lwjgl.opengl.GL15.glDeleteBuffers +import org.lwjgl.opengl.GL30 import java.awt.Color +import java.util.function.Function /** * Renders text and emoji glyphs using a shader-based font rendering system. * This class handles text and emoji rendering, shadow effects, and text scaling. */ -object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("renderer/font")) { - private val chars get() = RenderSettings.textFont - private val emojis get() = RenderSettings.emojiFont +object FontRenderer { + val vao by lazy { VertexArray(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) } + private val stringCache = Object2ObjectOpenHashMap, CachedString>() + private val shader by lazy { shader("renderer/font") } - private val shadowShift get() = RenderSettings.shadowShift * 10.0 - private val baselineOffset get() = RenderSettings.baselineOffset * 2.0f - 16f - private val gap get() = RenderSettings.gap * 0.5f - 0.8f + val chars get() = RenderSettings.textFont + val emojis get() = RenderSettings.emojiFont + + init { + listen(alwaysListen = true) { + invalidate() + } + + listen(alwaysListen = true) { + invalidate() + } + } /** * Renders a text string at a specified position with configurable color, scale, shadow, and emoji parsing @@ -53,7 +69,6 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende * @param color The color of the text. * @param scale The scale factor of the text. * @param shadow Whether to render a shadow for the text. - * @param parseEmoji Whether to parse and render emojis in the text. */ fun drawString( text: String, @@ -61,92 +76,27 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende color: Color = Color.WHITE, scale: Double = ClickGui.fontScale, shadow: Boolean = true, - parseEmoji: Boolean = LambdaMoji.isEnabled, shade: Boolean = false - ) = render(shade) { - shader["u_FontTexture"] = 0 - shader["u_EmojiTexture"] = 1 - shader["u_SDFMin"] = RenderSettings.sdfMin - shader["u_SDFMax"] = RenderSettings.sdfMax - - bind(chars, emojis) - - upload { - processText(text, color, scale, shadow, parseEmoji) { char, pos1, pos2, col, _ -> - buildGlyph(char, position, pos1, pos2, col) - } - } - } + ) = Matrices.push { + translate(position.x, position.y) + scale(scale, scale) - /** - * Renders a single glyph at the specified position with the given scale and color - * - * @param glyph The glyph information - * @param position The rendering position where the glyph will be drawn - * @param color The color of the glyph - * @param scale The scale factor of the glyph - */ - fun drawGlyph( - glyph: GlyphInfo, - position: Vec2d, - color: Color = Color.WHITE, - scale: Double = ClickGui.fontScale, - shade: Boolean = false - ) = render(shade) { + shader.use() + shader.shadeUniforms(shade) shader["u_FontTexture"] = 0 shader["u_EmojiTexture"] = 1 shader["u_SDFMin"] = RenderSettings.sdfMin shader["u_SDFMax"] = RenderSettings.sdfMax + shader["u_Color"] = color bind(chars, emojis) - val actualScale = getScaleFactor(scale) - val scaledSize = glyph.size * actualScale - - val posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - val pos1 = Vec2d(0.0, posY) * actualScale - val pos2 = pos1 + scaledSize - - upload { - buildGlyph(glyph, position, pos1, pos2, color) - } - } - - /** - * Renders a single glyph at a given position. - * - * @param glyph The glyph information to render. - * @param origin The position to start from - * @param pos1 The starting position of the glyph. - * @param pos2 The end position of the glyph - * @param color The color of the glyph. - */ - private fun VertexBuilder.buildGlyph( - glyph: GlyphInfo, - origin: Vec2d = Vec2d.ZERO, - pos1: Vec2d, - pos2: Vec2d, - color: Color, - ) { - val x1 = pos1.x + origin.x - val y1 = pos1.y + origin.y - val x2 = pos2.x + origin.x - val y2 = pos2.y + origin.y + val string = stringCache.computeIfAbsent(text to shadow, Function, CachedString> { + CachedString(it.first, it.second) + }) - buildQuad( - vertex { - vec3m(x1, y1).vec2(glyph.uv1.x, glyph.uv1.y).color(color) - }, - vertex { - vec3m(x1, y2).vec2(glyph.uv1.x, glyph.uv2.y).color(color) - }, - vertex { - vec3m(x2, y2).vec2(glyph.uv2.x, glyph.uv2.y).color(color) - }, - vertex { - vec3m(x2, y1).vec2(glyph.uv2.x, glyph.uv1.y).color(color) - } - ) + vao.linkVbo(string.vbo) + vao.renderIndices(string.ibo) } /** @@ -154,23 +104,12 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende * * @param text The text to measure. * @param scale The scale factor for the width calculation. - * @param parseEmoji Whether to include emojis in the width calculation. * @return The width of the text at the specified scale. */ - fun getWidth( - text: String, - scale: Double = ClickGui.fontScale, - parseEmoji: Boolean = LambdaMoji.isEnabled, - ): Double { - var width = 0.0 - var gaps = -1 - - processText(text, scale = scale, parseEmoji = parseEmoji) { char, _, _, _, isShadow -> - if (isShadow) return@processText - width += char.width; gaps++ - } - - return (width + gaps.coerceAtLeast(0) * gap) * getScaleFactor(scale) + fun getWidth(text: String, scale: Double = 1.0): Double { + // Todo: separate width cache? + val cache = stringCache[text to true] ?: stringCache[text to false] + return (cache?.cachedWidth ?: getStringWidth(text)) * scale } /** @@ -182,110 +121,17 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende * @param scale The scale factor for the height calculation. * @return The height of the text at the specified scale. */ - fun getHeight(scale: Double = 1.0) = chars.height * getScaleFactor(scale) * 0.7 - - /** - * Processes a text string by iterating over its characters and emojis, computing rendering positions, and invoking a block for each glyph - * - * @param text The text to iterate over. - * @param color The color of the text. - * @param scale The scale of the text. - * @param shadow Whether to render a shadow. - * @param parseEmoji Whether to parse and include emojis. - * @param block The function to apply to each character or emoji glyph. - */ - private fun processText( - text: String, - color: Color = Color.WHITE, - scale: Double = 1.0, - shadow: Boolean = RenderSettings.shadow, - parseEmoji: Boolean = LambdaMoji.isEnabled, - block: (GlyphInfo, Vec2d, Vec2d, Color, Boolean) -> Unit - ) { - val actualScale = getScaleFactor(scale) - val scaledGap = gap * actualScale - - val shadowColor = getShadowColor(color) - val emojiColor = color.setAlpha(color.a) - - var posX = 0.0 - var posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - - fun drawGlyph(info: GlyphInfo?, color: Color, isShadow: Boolean = false) { - if (info == null) return - - val scaledSize = info.size * actualScale - val pos1 = Vec2d(posX, posY) + shadowShift * actualScale * isShadow.toInt() - val pos2 = pos1 + scaledSize - - block(info, pos1, pos2, color, isShadow) - if (!isShadow) posX += scaledSize.x + scaledGap + fun getHeight(scale: Double = 1.0) = + chars.height * 0.7 * SCALE_FACTOR * scale + + fun invalidate() { + stringCache.values.forEach { + glDeleteBuffers(intArrayOf( + it.vbo.glBuffer.id, + it.ibo.glBuffer.id + )) } - val parsed = if (parseEmoji) emojis.parse(text) else mutableListOf() - - fun processTextSection(section: String, hasEmojis: Boolean) { - if (section.isEmpty()) return - if (!parseEmoji || parsed.isEmpty() || !hasEmojis) { - // Draw simple characters if no emojis are present - section.forEach { char -> - // Logic for control characters - when (char) { - '\n', '\r' -> { posX = 0.0; posY += chars.height * actualScale; return@forEach } - } - - val glyph = chars[char] ?: return@forEach - - if (shadow && RenderSettings.shadow) drawGlyph(glyph, shadowColor, true) - drawGlyph(glyph, color) - } - } else { - // Only compute the first parsed emoji to avoid duplication - // This is important in order to keep the parsed ranges valid - // If you do not this, you will get out of bounds positions - // due to slicing - val emoji = parsed.removeFirstOrNull() ?: return - - // Iterate the emojis from left to right - val start = section.indexOf(emoji) - val end = start + emoji.length - - val preEmojiText = section.substring(0, start) - val postEmojiText = section.substring(end) - - // Draw the text without emoji - processTextSection(preEmojiText, hasEmojis = false) - - // Draw the emoji - drawGlyph(emojis[emoji], emojiColor) - - // Process the rest of the text after the emoji - processTextSection(postEmojiText, hasEmojis = true) - } - } - - // Start processing the full text - processTextSection(text, hasEmojis = parsed.isNotEmpty()) + stringCache.clear() } - - /** - * Calculates the scale factor for the text based on the provided scale. - * - * @param scale The base scale factor. - * @return The adjusted scale factor. - */ - fun getScaleFactor(scale: Double): Double = scale * 8.5 / chars.height - - /** - * Calculates the shadow color by adjusting the brightness of the input color. - * - * @param color The original color. - * @return The modified shadow color. - */ - fun getShadowColor(color: Color): Color = Color( - (color.red * RenderSettings.shadowBrightness).toInt(), - (color.green * RenderSettings.shadowBrightness).toInt(), - (color.blue * RenderSettings.shadowBrightness).toInt(), - color.alpha - ) } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt index efd965def..9d41467f7 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt @@ -17,17 +17,18 @@ package com.lambda.graphics.renderer.gui -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.pipeline.VertexPipeline +import com.lambda.graphics.shader.Shader +import com.lambda.graphics.shader.Shader.Companion.shadeUniforms import com.lambda.graphics.shader.Shader.Companion.shader import com.lambda.util.math.Rect -import com.lambda.util.math.Vec2d import java.awt.Color import kotlin.math.min object RectRenderer { - private val filled = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_filled")) - private val outline = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_outline")) - private val glow = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_glow")) + private val filled = shader("renderer/rect_filled") + private val outline = shader("renderer/rect_outline") + private val glow = shader("renderer/rect_glow") fun filledRect( rect: Rect, @@ -40,7 +41,8 @@ object RectRenderer { rightBottom: Color = Color.WHITE, leftBottom: Color = Color.WHITE, shade: Boolean = false, - ) = filled.putRect( + ) = putRect( + filled, rect, 0.0, shade, @@ -69,7 +71,8 @@ object RectRenderer { ) { if (width < 0.01) return - outline.putRect( + putRect( + outline, rect, width * 0.25, shade, @@ -115,14 +118,15 @@ object RectRenderer { this.coerceAtMost(maxRadius) .coerceAtLeast(0.0) - glow.shader.use() - glow.shader["u_InnerRectWidth"] = innerSpread.coerceAtLeast(1.0) - glow.shader["u_InnerRoundLeftTop"] = leftTopInnerRadius .coerceAtLeast(leftTopOuterRadius) .clampRadius() - glow.shader["u_InnerRoundLeftBottom"] = leftBottomInnerRadius .coerceAtLeast(leftBottomOuterRadius) .clampRadius() - glow.shader["u_InnerRoundRightBottom"] = rightBottomInnerRadius.coerceAtLeast(rightBottomOuterRadius).clampRadius() - glow.shader["u_InnerRoundRightTop"] = rightTopInnerRadius .coerceAtLeast(rightTopOuterRadius) .clampRadius() + glow.use() + glow["u_InnerRectWidth"] = innerSpread.coerceAtLeast(1.0) + glow["u_InnerRoundLeftTop"] = leftTopInnerRadius .coerceAtLeast(leftTopOuterRadius) .clampRadius() + glow["u_InnerRoundLeftBottom"] = leftBottomInnerRadius .coerceAtLeast(leftBottomOuterRadius) .clampRadius() + glow["u_InnerRoundRightBottom"] = rightBottomInnerRadius.coerceAtLeast(rightBottomOuterRadius).clampRadius() + glow["u_InnerRoundRightTop"] = rightTopInnerRadius .coerceAtLeast(rightTopOuterRadius) .clampRadius() - glow.putRect( + putRect( + glow, rect, outerSpread.coerceAtLeast(1.0), shade, @@ -137,7 +141,8 @@ object RectRenderer { ) } - private fun AbstractGUIRenderer.putRect( + private fun putRect( + shader: Shader, rect: Rect, expandIn: Double, shade: Boolean, @@ -149,36 +154,29 @@ object RectRenderer { rightTop: Color, rightBottom: Color, leftBottom: Color, - ) = render(shade) { shader -> - val pos1 = rect.leftTop - val pos2 = rect.rightBottom + ) { + if (leftTop.alpha + rightTop.alpha + leftBottom.alpha + rightBottom.alpha == 0) return + + shader.use() + shader.shadeUniforms(shade) val expand = expandIn.coerceAtLeast(0.0) + 1 - val smoothing = 0.3 - val p1 = pos1 - expand - smoothing - val p2 = pos2 + expand + smoothing + val pos1 = rect.leftTop + val pos2 = rect.rightBottom val size = pos2 - pos1 val halfSize = size * 0.5 val maxRadius = min(halfSize.x, halfSize.y) - val uv1 = Vec2d( - -expand / size.x, - -expand / size.y - ) - - val uv2 = Vec2d( - 1.0 + (expand / size.x), - 1.0 + (expand / size.y) - ) - fun Double.clampRadius() = this.coerceAtMost(maxRadius) .coerceAtLeast(0.0) // Size of the rectangle + shader["u_Pos"] = pos1 shader["u_Size"] = size + shader["u_Expand"] = expand // Round radius shader["u_RoundLeftTop"] = leftTopRadius.clampRadius() @@ -186,24 +184,15 @@ object RectRenderer { shader["u_RoundRightBottom"] = rightBottomRadius.clampRadius() shader["u_RoundRightTop"] = rightTopRadius.clampRadius() + // Color + shader["u_ColorLeftTop"] = leftTop + shader["u_ColorLeftBottom"] = leftBottom + shader["u_ColorRightBottom"] = rightBottom + shader["u_ColorRightTop"] = rightTop + // For glow & outline only shader["u_RectWidth"] = expandIn - upload { - buildQuad( - vertex { - vec3m(p1.x, p1.y).vec2(uv1.x, uv1.y).color(leftTop) - }, - vertex { - vec3m(p1.x, p2.y).vec2(uv1.x, uv2.y).color(leftBottom) - }, - vertex { - vec3m(p2.x, p2.y).vec2(uv2.x, uv2.y).color(rightBottom) - }, - vertex { - vec3m(p2.x, p1.y).vec2(uv2.x, uv1.y).color(rightTop) - } - ) - } + VertexPipeline.renderStaticRect() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt new file mode 100644 index 000000000..09e6dd282 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt @@ -0,0 +1,186 @@ +/* + * Copyright 2025 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.renderer.gui.font + +import com.lambda.graphics.pipeline.PersistentBuffer +import com.lambda.graphics.pipeline.VertexBuilder +import com.lambda.graphics.renderer.gui.FontRenderer +import com.lambda.graphics.renderer.gui.FontRenderer.chars +import com.lambda.graphics.renderer.gui.FontRenderer.emojis +import com.lambda.graphics.renderer.gui.FontRenderer.getHeight +import com.lambda.graphics.renderer.gui.font.core.GlyphInfo +import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get +import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height +import com.lambda.module.modules.client.LambdaMoji +import com.lambda.module.modules.client.RenderSettings +import com.lambda.module.modules.client.RenderSettings.baselineOffset +import com.lambda.module.modules.client.RenderSettings.gap +import com.lambda.module.modules.client.RenderSettings.shadowShift +import com.lambda.util.math.MathUtils.toInt +import com.lambda.util.math.Vec2d +import org.lwjgl.opengl.GL32C.GL_ARRAY_BUFFER +import org.lwjgl.opengl.GL32C.GL_ELEMENT_ARRAY_BUFFER +import java.awt.Color + +// ToDo: use different attribute set based on the string simplicity +class CachedString( + string: String, + shadow: Boolean +) { + val vbo: PersistentBuffer + val ibo: PersistentBuffer + + val cachedWidth = getStringWidth(string) + + init { + /* Build vertices and indices */ + val builder = VertexBuilder().use { + processText(string, shadow) { glyph, pos1, pos2, c, _ -> + val x1 = pos1.x + val y1 = pos1.y + val x2 = pos2.x + val y2 = pos2.y + + buildQuad( + vertex { + vec2(x1, y1).vec2(glyph.uv1.x, glyph.uv1.y).color(c) + }, + vertex { + vec2(x1, y2).vec2(glyph.uv1.x, glyph.uv2.y).color(c) + }, + vertex { + vec2(x2, y2).vec2(glyph.uv2.x, glyph.uv2.y).color(c) + }, + vertex { + vec2(x2, y1).vec2(glyph.uv2.x, glyph.uv1.y).color(c) + } + ) + } + } + + /* Create gl buffers and upload */ + val vao = FontRenderer.vao + vbo = PersistentBuffer(GL_ARRAY_BUFFER, vao.attributes.stride, builder.vertices.size) + ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, UInt.SIZE_BYTES, builder.indices.size) + vao.linkVbo(vbo) + + builder.uploadVertices(vbo.byteBuffer) + vbo.upload() + + builder.uploadIndices(ibo.byteBuffer) + ibo.upload() + } + + companion object { + val SCALE_FACTOR get() = 8.5 / chars.height + + /** + * Processes a text string by iterating over its characters and emojis, computing rendering positions, and invoking a block for each glyph + * + * @param text The text to iterate over. + * @param shadow Whether to render a shadow. + * @param block The function to apply to each character or emoji glyph. + */ + fun processText( + text: String, + shadow: Boolean, + block: (GlyphInfo, Vec2d, Vec2d, Color, Boolean) -> Unit + ) { + val scaledGap = gap * SCALE_FACTOR + + var posX = 0.0 + var posY = baselineOffset * SCALE_FACTOR - getHeight() * 0.5 + + fun drawGlyph(info: GlyphInfo?, color: Color, isShadow: Boolean = false) { + if (info == null) return + + val scaledSize = info.size * SCALE_FACTOR + val pos1 = Vec2d(posX, posY) + shadowShift * SCALE_FACTOR * isShadow.toInt() + val pos2 = pos1 + scaledSize + + block(info, pos1, pos2, color, isShadow) + if (!isShadow) posX += scaledSize.x + scaledGap + } + + val parsed = if (LambdaMoji.isEnabled) emojis.parse(text) else mutableListOf() + + fun processTextSection(section: String, hasEmojis: Boolean) { + if (section.isEmpty()) return + if (!LambdaMoji.isEnabled || parsed.isEmpty() || !hasEmojis) { + // Draw simple characters if no emojis are present + section.forEach { char -> + // Logic for control characters + when (char) { + '\n', '\r' -> { posX = 0.0; posY += chars.height * SCALE_FACTOR; return@forEach } + } + + val glyph = chars[char] ?: return@forEach + + // ToDo: Implement colorcodes or remove color from attributes + val color = Color.WHITE + val shadowColor = Color( + (color.red * RenderSettings.shadowBrightness).toInt(), + (color.green * RenderSettings.shadowBrightness).toInt(), + (color.blue * RenderSettings.shadowBrightness).toInt(), + color.alpha + ) + + if (shadow) drawGlyph(glyph, shadowColor, true) + drawGlyph(glyph, color) + } + } else { + // Only compute the first parsed emoji to avoid duplication + // This is important in order to keep the parsed ranges valid + // If you do not this, you will get out of bounds positions + // due to slicing + val emoji = parsed.removeFirstOrNull() ?: return + + // Iterate the emojis from left to right + val start = section.indexOf(emoji) + val end = start + emoji.length + + val preEmojiText = section.substring(0, start) + val postEmojiText = section.substring(end) + + // Draw the text without emoji + processTextSection(preEmojiText, hasEmojis = false) + + // Draw the emoji + drawGlyph(emojis[emoji], Color.WHITE) + + // Process the rest of the text after the emoji + processTextSection(postEmojiText, hasEmojis = true) + } + } + + // Start processing the full text + processTextSection(text, hasEmojis = parsed.isNotEmpty()) + } + + fun getStringWidth(string: String): Double { + var width = 0.0 + var gaps = -1 + + processText(string, false) { char, _, _, _, _ -> + width += char.width; gaps++ + } + + return (width + gaps.coerceAtLeast(0) * gap) * SCALE_FACTOR + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt index 3ebcb54bc..17a390f8a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt +++ b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt @@ -21,12 +21,17 @@ import com.lambda.graphics.RenderMain import com.lambda.graphics.shader.ShaderUtils.createShaderProgram import com.lambda.graphics.shader.ShaderUtils.loadShader import com.lambda.graphics.shader.ShaderUtils.uniformMatrix +import com.lambda.module.modules.client.GuiSettings +import com.lambda.module.modules.client.GuiSettings.primaryColor +import com.lambda.module.modules.client.GuiSettings.secondaryColor +import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d import it.unimi.dsi.fastutil.objects.Object2IntMap import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.util.math.Vec3d import org.joml.Matrix4f +import org.lwjgl.glfw.GLFW import org.lwjgl.opengl.GL20C.* import java.awt.Color @@ -88,5 +93,20 @@ class Shader private constructor(name: String) { shaderCache.getOrPut(path) { Shader(path) } + + fun Shader.shadeUniforms(shade: Boolean) { + val shader = this + + shader["u_Shade"] = shade.toInt().toDouble() + if (shade) { + shader["u_ShadeTime"] = GLFW.glfwGetTime() * GuiSettings.colorSpeed * 5.0 + shader["u_ShadeColor1"] = primaryColor + shader["u_ShadeColor2"] = secondaryColor + + val size = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) + //if (this is FontRenderer) size *= 5.0 + shader["u_ShadeSize"] = size + } + } } } diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt b/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt index 86cbfc6c5..399838df1 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt @@ -30,8 +30,9 @@ class TextField( owner: Layout, ) : Layout(owner) { @UIRenderPr0p3rty var text = "" - @UIRenderPr0p3rty var color: Color = Color.WHITE @UIRenderPr0p3rty var scale = 1.0 + + @UIRenderPr0p3rty var color: Color = Color.WHITE @UIRenderPr0p3rty var shadow = true @UIRenderPr0p3rty var textHAlignment = HAlign.LEFT diff --git a/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt b/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt index c6c4da26d..8d80e8842 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt @@ -370,28 +370,37 @@ open class Layout( // Update children children.forEach { child -> - if (e is GuiEvent.Render) return@forEach - if (e is GuiEvent.Update && !updateChildren) return@forEach - - if (e is GuiEvent.MouseClick) { - val newAction = if (child.isHovered) e.action else Mouse.Action.Release - - val newEvent = GuiEvent.MouseClick(e.button, newAction, e.mouse) - child.onEvent(newEvent) - return@forEach + when(e) { + is GuiEvent.Render -> return@forEach + is GuiEvent.Update -> { + if (!updateChildren) return@forEach + } + is GuiEvent.MouseMove -> { + if (!isHovered && !isPressed) return@forEach + } + is GuiEvent.MouseClick -> { + val newAction = if (child.isHovered) e.action else Mouse.Action.Release + val newEvent = GuiEvent.MouseClick(e.button, newAction, e.mouse) + child.onEvent(newEvent) + return@forEach + } + else -> {} } child.onEvent(e) } - if (e is GuiEvent.Render) { - val block = { - renderActions.forEach { it(this) } - children.forEach { it.onEvent(e) } - } + when (e) { + is GuiEvent.Render -> { + val block = { + renderActions.forEach { it(this) } + children.forEach { it.onEvent(e) } + } - if (!properties.scissor) block() - else ScissorAdapter.scissor(scissorRect, block) + if (!properties.scissor) block() + else ScissorAdapter.scissor(scissorRect, block) + } + else -> {} } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt b/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt index 8ce70690d..cf54bab9f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt @@ -20,9 +20,8 @@ package com.lambda.module.modules.client import com.lambda.Lambda.mc import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.renderer.gui.FontRenderer.drawGlyph -import com.lambda.graphics.renderer.gui.font.core.GlyphInfo -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get +import com.lambda.graphics.renderer.gui.FontRenderer +import com.lambda.graphics.renderer.gui.FontRenderer.drawString import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.Vec2d @@ -39,16 +38,20 @@ object LambdaMoji : Module( ) { val suggestions by setting("Chat Suggestions", true) - private val renderQueue = mutableListOf>() + private val renderQueue = mutableListOf>() init { listen { - renderQueue.forEach { (glyph, position, color) -> - drawGlyph(glyph, position, color) + renderQueue.forEach { (string, position, color) -> + drawString(string, position, color) } renderQueue.clear() } + + onToggle { + FontRenderer.invalidate() + } } // FixMe: Doesn't render properly when the chat scale is modified @@ -77,8 +80,7 @@ object LambdaMoji : Module( else -> Color(255, 255, 255, (color shr 24 and 0xFF)) } - val glyph = RenderSettings.emojiFont[emoji] ?: return@forEach - renderQueue.add(Triple(glyph, Vec2d(x + width, y), trueColor)) + renderQueue.add(Triple(emoji, Vec2d(x + width, y), trueColor)) // Replace the emoji with whitespaces depending on the player's settings raw = raw.replaceFirst(emoji, " ") 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 b7f228fb2..67bcc46eb 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 @@ -17,6 +17,7 @@ package com.lambda.module.modules.client +import com.lambda.graphics.renderer.gui.FontRenderer import com.lambda.graphics.renderer.gui.font.core.LambdaEmoji import com.lambda.graphics.renderer.gui.font.core.LambdaFont import com.lambda.module.Module @@ -30,17 +31,34 @@ object RenderSettings : Module( ) { private val page by setting("Page", Page.Font) - // General - val useMemoryMapping by setting("Use Memory Mapping", true) { page == Page.General} - // Font - val textFont by setting("Text Font", LambdaFont.FiraSansRegular) { page == Page.Font } - val emojiFont by setting("Emoji Font", LambdaEmoji.Twemoji) { page == Page.Font } - val shadow by setting("Shadow", true) { page == Page.Font } - val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01) { page == Page.Font && shadow } - val shadowShift by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05) { page == Page.Font && shadow } - val gap by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font } - val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font } + val textFont by setting("Text Font", LambdaFont.FiraSansRegular) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val emojiFont by setting("Emoji Font", LambdaEmoji.Twemoji) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val shadowShift get() = shadowShift0 * 10.0 + private val shadowShift0 by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val gap get() = gap0 * 0.5f - 0.8f + private val gap0 by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val baselineOffset get() = baselineOffset0 * 2.0f - 16f + private val baselineOffset0 by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + val highlightColor by setting("Text Highlight Color", Color(214, 55, 87), visibility = { page == Page.Font }) val sdfMin by setting("SDF Min", 0.4, 0.0..1.0, 0.01, visibility = { page == Page.Font }) val sdfMax by setting("SDF Max", 1.0, 0.0..1.0, 0.01, visibility = { page == Page.Font }) @@ -52,7 +70,6 @@ object RenderSettings : Module( val outlineWidth by setting("Outline Width", 1.0, 0.1..5.0, 0.1, "Width of block outlines", unit = "px") { page == Page.ESP } private enum class Page { - General, Font, ESP, } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 9c2cd573c..81f674a6d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -497,7 +497,11 @@ object CrystalAura : Module( } fun buildDebug() { - withVertexTransform(buildWorldProjection(blockPos.crystalPosition, 0.4, Matrices.ProjRotationMode.TO_CAMERA)) { + Matrices.push { + peek().mul( + buildWorldProjection(blockPos.crystalPosition, 0.4, Matrices.ProjRotationMode.TO_CAMERA) + ) + val lines = arrayOf( "Decision: ${actionType.name} ${priority.roundToStep(0.01)}", "", diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt index 006241337..82dc92555 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt @@ -68,6 +68,7 @@ object BlockESP : Module( init { onToggle { if (barrier) mc.worldRenderer.reload() + esp.rebuild() } } @@ -77,8 +78,8 @@ object BlockESP : Module( if (state.block !in blocks) return@newChunkedESP val sides = if (mesh) { - buildSideMesh(position) { - world.getBlockState(it).block in blocks + buildSideMesh(position) { neighbour -> + world.getBlockState(neighbour).block in blocks } } else DirectionMask.ALL diff --git a/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl b/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl index f5a68cd83..3b7f5509d 100644 --- a/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl +++ b/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl @@ -1,5 +1,4 @@ attributes { - vec2 pos; vec2 uv; }; @@ -27,4 +26,4 @@ void fragment() { sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_5 * u_TexelSize.x, 0.0)) * WEIGHT_5; color = sum; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl b/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl index 8c498b087..d0e2becbe 100644 --- a/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl +++ b/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl @@ -1,5 +1,4 @@ attributes { - vec2 pos; vec2 uv; }; @@ -27,4 +26,4 @@ void fragment() { sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_5 * u_TexelSize.y)) * WEIGHT_5; color = sum; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl index 6dbf076e2..0572a9dba 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl @@ -1,5 +1,5 @@ attributes { - vec4 pos; + vec2 pos; vec2 uv; vec4 color; }; @@ -7,13 +7,17 @@ attributes { uniforms { sampler2D u_FontTexture; # fragment sampler2D u_EmojiTexture; # fragment - float u_SDFMin; # fragment - float u_SDFMax; # fragment + + float u_SDFMin; # fragment + float u_SDFMax; # fragment + + vec4 u_Color; # vertex }; export { - vec2 v_TexCoord; # uv - vec4 v_Color; # color + core gl_Position; # u_ProjModel * vec4(pos, 0.0, 1.0) + vec2 v_TexCoord; # uv + vec4 v_Color; # color * u_Color }; #include "sdf" @@ -30,4 +34,4 @@ void fragment() { float sdf = sdf(texture(u_FontTexture, v_TexCoord).r, u_SDFMin, u_SDFMax); color = vec4(1.0, 1.0, 1.0, sdf) * shade * v_Color; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl index 7ee5ec204..b658c56a3 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl @@ -1,13 +1,13 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; #include "rect" void fragment() { + if (v_Color.a == 0.0) discard; + float distance = signedDistance(); float alpha = 1 - smoothstep(-SMOOTHING, SMOOTHING, distance); color = v_Color * vec4(1.0, 1.0, 1.0, alpha) * shade + noise; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl index 094f9c373..2a0c1e962 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl @@ -1,7 +1,5 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; uniforms { @@ -17,6 +15,8 @@ uniforms { #include "rect" void fragment() { + if (v_Color.a == 0.0) discard; + float distance = signedDistance(); float innerDistance = signedDistance(vec4( u_InnerRoundRightBottom, @@ -37,4 +37,4 @@ void fragment() { smoothstep(0.0, u_RectWidth, bloomDistance); color = v_Color * vec4(1.0, 1.0, 1.0, bloomAlpha * glowAlpha) * shade + noise; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl index d27ac5873..3ea1edb97 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl @@ -1,7 +1,5 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; uniforms { @@ -11,8 +9,10 @@ uniforms { #include "rect" void fragment() { + if (v_Color.a == 0.0) discard; + float distance = signedDistance(); float innerAlpha = smoothstep(-u_RectWidth - SMOOTHING, -u_RectWidth + SMOOTHING, distance); float outerAlpha = 1 - smoothstep(u_RectWidth - SMOOTHING, u_RectWidth + SMOOTHING, distance); color = v_Color * vec4(1.0, 1.0, 1.0, min(innerAlpha, outerAlpha)) * shade + noise; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl b/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl index 3a13c96c3..ada98e79a 100644 --- a/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl +++ b/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl @@ -1,5 +1,4 @@ attributes { - vec2 pos; vec2 uv; }; @@ -7,10 +6,13 @@ uniforms { sampler2D u_Texture; # fragment vec2 u_TexelSize; # global vec2 u_Extend; # vertex + + vec2 u_Position1; # vertex + vec2 u_Position2; # vertex }; export { - core gl_Position; # u_ProjModel * vec4(pos, 0.0, 1.0) + vec4(u_TexelSize * u_Extend * vec2(uv.x - 0.5, 0.5 - uv.y) * 2 * 12, 0.0, 0.0) + core gl_Position; # u_ProjModel * vec4(mix(u_Position1, u_Position2, uv), 0.0, 1.0) + vec4(u_TexelSize * u_Extend * vec2(uv.x - 0.5, 0.5 - uv.y) * 2 * 12, 0.0, 0.0) vec2 v_TexCoord; # gl_Position.xy * 0.5 + 0.5 }; @@ -28,4 +30,4 @@ export { #define OFFSET_2 5.41538461538461 #define OFFSET_3 7.38461538461538 #define OFFSET_4 9.35384615384615 -#define OFFSET_5 11.3230769230769 \ No newline at end of file +#define OFFSET_5 11.3230769230769 diff --git a/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl b/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl index 4fe1b2220..ed1469b07 100644 --- a/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl +++ b/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl @@ -1,21 +1,29 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; uniforms { - vec2 u_Size; # fragment - float u_RoundLeftTop; # fragment float u_RoundLeftBottom; # fragment float u_RoundRightBottom; # fragment float u_RoundRightTop; # fragment + + vec4 u_ColorLeftTop; # vertex + vec4 u_ColorLeftBottom; # vertex + vec4 u_ColorRightBottom; # vertex + vec4 u_ColorRightTop; # vertex + + vec2 u_Pos; # vertex + vec2 u_Size; # global + + float u_Expand; # vertex }; export { - vec2 v_TexCoord; # uv - vec4 v_Color; # color + vec2 v_TexCoord; # mix(-u_Expand / u_Size, 1.0 + (u_Expand / u_Size), uv) + core gl_Position; # u_ProjModel * vec4(mix(u_Pos - 0.3, u_Pos + u_Size + 0.3, v_TexCoord), 0.0, 1.0) + + vec4 v_Color; # mix(mix(u_ColorLeftTop, u_ColorRightTop, uv.x), mix(u_ColorLeftBottom, u_ColorRightBottom, uv.x), uv.y) }; #include "shade" From 66c4b0852c7079311dfb29349ba7ebf6fd7d6043 Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 9 May 2025 17:19:01 +0200 Subject: [PATCH 4/6] Try to add lines --- .../buffer/vertex/attributes/VertexAttrib.kt | 8 + .../lambda/graphics/pipeline/VertexBuilder.kt | 18 ++ .../esp/builders/DynamicESPLineBuilders.kt | 174 +++++++++++++ .../esp/builders/StaticESPLineBuilders.kt | 137 ++++++++++ .../graphics/renderer/gui/LineRenderer.kt | 240 ++++++++++++++++++ .../com/lambda/gui/component/core/Line.kt | 73 ++++++ .../lambda/gui/component/core/LineLayout.kt | 50 ++++ .../lambda/module/modules/debug/RenderTest.kt | 80 +++++- .../assets/lambda/shaders/renderer/line.glsl | 27 ++ .../lambda/shaders/renderer/multiline.glsl | 44 ++++ 10 files changed, 848 insertions(+), 3 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt create mode 100644 common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt create mode 100644 common/src/main/kotlin/com/lambda/gui/component/core/Line.kt create mode 100644 common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt create mode 100644 common/src/main/resources/assets/lambda/shaders/renderer/line.glsl create mode 100644 common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl 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 dabbfbf68..90c95d55e 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 @@ -83,6 +83,14 @@ sealed class VertexAttrib( Vec3, Vec2, Color ) + object LINE : Group( + Vec2, Float, Color + ) + + object MULTILINE : Group( + Vec2, Float, Float, Float, Color + ) + val stride = attributes.sumOf { attribute -> attribute.size } diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt index a882f88d1..676e27fff 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt @@ -90,6 +90,24 @@ class VertexBuilder( this.indices += index2 } + /** + * Adds triangle indices using 3 vertices + */ + fun buildTriangle( + index1: Int, index2: Int, index3: Int + ) { + direct?.let { + it.indices.putInt(index1) + it.indices.putInt(index2) + it.indices.putInt(index3) + return + } + + this.indices += index1 + this.indices += index2 + this.indices += index3 + } + /** * Creates a collection of indices from varargs * @return List of provided indices for element array buffer diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt new file mode 100644 index 000000000..4cf589439 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt @@ -0,0 +1,174 @@ +/* + * Copyright 2025 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.renderer.esp.builders + +import com.lambda.graphics.RenderMain +import com.lambda.graphics.renderer.esp.DynamicAABB +import com.lambda.graphics.renderer.esp.impl.DynamicESPRenderer +import com.lambda.graphics.renderer.gui.LineRenderer +import net.minecraft.util.math.Vec3d +import java.awt.Color +import org.joml.Vector2d +import org.joml.Vector4f + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param points List of 3D points to connect with lines + * @param color Color of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun DynamicESPRenderer.drawLine( + points: List, + color: Color, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + if (points.size < 2) return + + // Project 3D points to 2D screen coordinates + val screenPoints = points.mapNotNull { point -> + val screenPos = project3DTo2D(point) ?: return@mapNotNull null + LineRenderer.Point(Vector2d(screenPos.x, screenPos.y), color) + } + + if (screenPoints.size < 2) return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + screenPoints.forEach { point -> + point(point.pos, point.color) + } + } + } +} + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param start Starting point of the line + * @param end Ending point of the line + * @param startColor Color at the start of the line + * @param endColor Color at the end of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun DynamicESPRenderer.drawLine( + start: Vec3d, + end: Vec3d, + startColor: Color, + endColor: Color = startColor, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + // Project 3D points to 2D screen coordinates + val startScreen = project3DTo2D(start) ?: return + val endScreen = project3DTo2D(end) ?: return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + point(Vector2d(startScreen.x, startScreen.y), startColor) + point(Vector2d(endScreen.x, endScreen.y), endColor) + } + } +} + +/** + * Draws a line between two dynamic boxes in 3D space using the LineRenderer. + * + * @param box1 First dynamic box + * @param box2 Second dynamic box + * @param color Color of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun DynamicESPRenderer.drawLineBetweenBoxes( + box1: DynamicAABB, + box2: DynamicAABB, + color: Color, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + val box1Pair = box1.getBoxPair() ?: return + val box2Pair = box2.getBoxPair() ?: return + + val center1 = Vec3d( + (box1Pair.first.minX + box1Pair.first.maxX) / 2, + (box1Pair.first.minY + box1Pair.first.maxY) / 2, + (box1Pair.first.minZ + box1Pair.first.maxZ) / 2 + ) + + val center2 = Vec3d( + (box2Pair.first.minX + box2Pair.first.maxX) / 2, + (box2Pair.first.minY + box2Pair.first.maxY) / 2, + (box2Pair.first.minZ + box2Pair.first.maxZ) / 2 + ) + + drawLine(center1, center2, color, color, width, dashiness, dashPeriod) +} + +/** + * Projects a 3D point to 2D screen coordinates. + * Returns null if the point is behind the camera or outside the screen. + */ +private fun project3DTo2D(point: Vec3d): Vector2d? { + // Create a 4D vector from the 3D point + val vec4 = Vector4f( + point.x.toFloat(), + point.y.toFloat(), + point.z.toFloat(), + 1f + ) + + // Transform the point using the projection-model matrix + RenderMain.projModel.transform(vec4) + + // Check if the point is behind the camera + if (vec4.w <= 0f) return null + + // Perform perspective division + vec4.div(vec4.w) + + // Convert from normalized device coordinates (-1 to 1) to screen coordinates (0 to screen width/height) + val screenX = (vec4.x * 0.5f + 0.5f) * RenderMain.screenSize.x.toFloat() + val screenY = (1f - (vec4.y * 0.5f + 0.5f)) * RenderMain.screenSize.y.toFloat() + + // Check if the point is outside the screen + if (screenX < 0 || screenX > RenderMain.screenSize.x.toFloat() || screenY < 0 || screenY > RenderMain.screenSize.y.toFloat()) { + return null + } + + return Vector2d(screenX.toDouble(), screenY.toDouble()) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt new file mode 100644 index 000000000..7ec06c868 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2025 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.renderer.esp.builders + +import com.lambda.graphics.RenderMain +import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer +import com.lambda.graphics.renderer.gui.LineRenderer +import net.minecraft.util.math.Vec3d +import java.awt.Color +import org.joml.Vector2d +import org.joml.Vector4f + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param points List of 3D points to connect with lines + * @param color Color of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun StaticESPRenderer.drawLine( + points: List, + color: Color, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + if (points.size < 2) return + + // Project 3D points to 2D screen coordinates + val screenPoints = points.mapNotNull { point -> + val screenPos = project3DTo2D(point) ?: return@mapNotNull null + LineRenderer.Point(Vector2d(screenPos.x, screenPos.y), color) + } + + if (screenPoints.size < 2) return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + screenPoints.forEach { point -> + point(point.pos, point.color) + } + } + } +} + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param start Starting point of the line + * @param end Ending point of the line + * @param startColor Color at the start of the line + * @param endColor Color at the end of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun StaticESPRenderer.drawLine( + start: Vec3d, + end: Vec3d, + startColor: Color, + endColor: Color = startColor, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + // Project 3D points to 2D screen coordinates + val startScreen = project3DTo2D(start) ?: return + val endScreen = project3DTo2D(end) ?: return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + point(Vector2d(startScreen.x, startScreen.y), startColor) + point(Vector2d(endScreen.x, endScreen.y), endColor) + } + } +} + +/** + * Projects a 3D point to 2D screen coordinates. + * Returns null if the point is behind the camera or outside the screen. + */ +private fun project3DTo2D(point: Vec3d): Vector2d? { + // Create a 4D vector from the 3D point + val vec4 = Vector4f( + point.x.toFloat(), + point.y.toFloat(), + point.z.toFloat(), + 1f + ) + + // Transform the point using the projection-model matrix + RenderMain.projModel.transform(vec4) + + // Check if the point is behind the camera + if (vec4.w <= 0f) return null + + // Perform perspective division + vec4.div(vec4.w) + + // Convert from normalized device coordinates (-1 to 1) to screen coordinates (0 to screen width/height) + val screenX = (vec4.x * 0.5f + 0.5f) * RenderMain.screenSize.x.toFloat() + val screenY = (1f - (vec4.y * 0.5f + 0.5f)) * RenderMain.screenSize.y.toFloat() + + // Check if the point is outside the screen + if (screenX < 0 || screenX > RenderMain.screenSize.x.toFloat() || screenY < 0 || screenY > RenderMain.screenSize.y.toFloat()) { + return null + } + + return Vector2d(screenX.toDouble(), screenY.toDouble()) +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt new file mode 100644 index 000000000..59c06bd74 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt @@ -0,0 +1,240 @@ +package com.lambda.graphics.renderer.gui + +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.pipeline.VertexBuilder +import com.lambda.graphics.pipeline.VertexPipeline +import com.lambda.graphics.shader.Shader.Companion.shader +import org.joml.Vector2d +import java.awt.Color +import kotlin.math.* + +object LineRenderer { + private val pipelineSingle = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.LINE) + private val shaderSingle = shader("renderer/line") + + private val pipelineMulti = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.MULTILINE) + private val shaderMulti = shader("renderer/multiline") + + fun lines( + dashiness: Double = 1.0, + dashPeriod: Double = 1.0, + batching: Boolean = false, + block: MultiLineBuilder.() -> Unit + ): VertexPipeline { + val lineBuilder = MultiLineBuilder().apply(block) + + if (batching) { + shaderSingle.use() + shaderSingle["u_Dashiness"] = dashiness + shaderSingle["u_DashPeriod"] = dashPeriod * 100.0 + + pipelineSingle.upload { + lineBuilder.lines.forEach { data -> + shaderSingle["u_Width"] = data.width + putLines(this, data, false) + } + } + + return pipelineSingle + } + + shaderMulti.use() + shaderMulti["u_Dashiness"] = dashiness + shaderMulti["u_DashPeriod"] = dashPeriod * 100.0 + + lineBuilder.lines.forEach { data -> + shaderMulti["u_Length"] = -1.0 + + pipelineMulti.upload { + putLines(this, data, true) + } + } + + return pipelineMulti + } + + private fun putLines( + builder: VertexBuilder, + data: MultiLineBuilder.LineData, + putDistance: Boolean + ) { + val points = data.points + val width = data.width + + var dist = 0.0 + val lines = Array(points.size - 1) { i -> + val p1 = points[i] + val p2 = points[i + 1] + + var normalX = p1.pos.y - p2.pos.y + var normalY = p2.pos.x - p1.pos.x + + val length = sqrt(normalX * normalX + normalY * normalY) + normalX /= length + normalY /= length + + val normal = Vector2d(normalX, normalY) + val halfWidth = width / 2.0 + val p11 = Vector2d(p1.pos.x + normal.x * halfWidth, p1.pos.y + normal.y * halfWidth) + val p12 = Vector2d(p1.pos.x - normal.x * halfWidth, p1.pos.y - normal.y * halfWidth) + val p21 = Vector2d(p2.pos.x + normal.x * halfWidth, p2.pos.y + normal.y * halfWidth) + val p22 = Vector2d(p2.pos.x - normal.x * halfWidth, p2.pos.y - normal.y * halfWidth) + + dist += length + Line(p1, p2, length, dist, p11, p12, p21, p22) + } + + val joints = mutableListOf Int, () -> Int, () -> Int>>() + repeat(lines.size - 1) { i -> + val current = lines[i] + val next = lines[i + 1] + + findIntersection( + current.p11, current.p21, + next.p21, next.p11 + )?.also { + current.p21 = it + next.p11 = it + next.connection = false + + joints.add(Triple(current::p22i, current::p21i, next::p12i)) + } ?: findIntersection( + current.p12, current.p22, + next.p22, next.p12 + )?.also { + current.p22 = it + next.p12 = it + next.connection = true + + joints.add(Triple(current::p21i, next::p11i, current::p22i)) + } + } + + // Render lines + builder.use { + lines.forEachIndexed { i, line -> + val prev = lines.getOrNull(i - 1) + + line.p11i = vertex { + vec2(line.p11.x, line.p11.y) + if (putDistance) float(prev?.distance ?: 0.0) + if (putDistance) float(width) + float(1.0) + color(line.point1.color) + } + + line.p12i = vertex { + vec2(line.p12.x, line.p12.y) + if (putDistance) float(prev?.distance ?: 0.0) + if (putDistance) float(width) + float(0.0) + color(line.point1.color) + } + + line.p21i = vertex { + vec2(line.p21.x, line.p21.y) + if (putDistance) float(line.distance) + if (putDistance) float(width) + float(1.0) + color(line.point2.color) + } + + line.p22i = vertex { + vec2(line.p22.x, line.p22.y) + if (putDistance) float(line.distance) + if (putDistance) float(width) + float(0.0) + color(line.point2.color) + } + + buildQuad(line.p11i, line.p21i, line.p22i, line.p12i) + } + + joints.forEach { joint -> + buildTriangle(joint.first(), joint.second(), joint.third()) + } + } + } + + private fun findIntersection( + firstP1: Vector2d, + firstP2: Vector2d, + secondP1: Vector2d, + secondP2: Vector2d, + ): Vector2d? { + val denominator = (firstP1.x - firstP2.x) * (secondP1.y - secondP2.y) - + (firstP1.y - firstP2.y) * (secondP1.x - secondP2.x) + + if (abs(denominator) < 1e-9) return null + + val det1 = firstP1.x * firstP2.y - firstP1.y * firstP2.x + val det2 = secondP1.x * secondP2.y - secondP1.y * secondP2.x + + val x = (det1 * (secondP1.x - secondP2.x) - (firstP1.x - firstP2.x) * det2) / denominator + val y = (det1 * (secondP1.y - secondP2.y) - (firstP1.y - firstP2.y) * det2) / denominator + + fun isPointOnSegment(x: Double, y: Double, p1: Vector2d, p2: Vector2d): Boolean { + val crossProduct = (p2.x - p1.x) * (y - p1.y) - (p2.y - p1.y) * (x - p1.x) + if (abs(crossProduct) > 1e-9) return false + + val dotProduct = (x - p1.x) * (p2.x - p1.x) + (y - p1.y) * (p2.y - p1.y) + if (dotProduct < 0) return false + + val squaredLength = (p2.x - p1.x).pow(2) + (p2.y - p1.y).pow(2) + if (dotProduct > squaredLength) return false + + return true + } + + if (!isPointOnSegment(x, y, firstP1, firstP2)) return null + if (!isPointOnSegment(x, y, secondP1, secondP2)) return null + return Vector2d(x, y) + } + + data class Point( + val pos: Vector2d, + val color: Color + ) + + private data class Line( + val point1: Point, + val point2: Point, + val length: Double, + var distance: Double, + var p11: Vector2d, + var p12: Vector2d, + var p21: Vector2d, + var p22: Vector2d + ) { + + var p11i = 0 + var p12i = 0 + var p21i = 0 + var p22i = 0 + var connection: Boolean? = null + } + + class MultiLineBuilder{ + val lines = mutableListOf() + + fun line(width: Double, block: LineBuilder.() -> Unit) { + lines.add(LineData(width).apply { + LineBuilder(this).apply(block) + }) + } + + class LineBuilder( + private val data: LineData + ) { + fun point(position: Vector2d, color: Color) = + data.points.add(Point(position, color)) + } + + class LineData( + val width: Double + ) { + val points = mutableListOf() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/Line.kt b/common/src/main/kotlin/com/lambda/gui/component/core/Line.kt new file mode 100644 index 000000000..7f7563a4f --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/core/Line.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2025 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.gui.component.core + +import com.lambda.graphics.renderer.gui.LineRenderer +import com.lambda.gui.component.layout.Layout +import org.joml.Vector2d +import java.awt.Color + +class Line( + owner: Layout +) : LineLayout(owner) { + init { + onRender { + if (points.size >= 2) { + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(lineWidth) { + points.forEach { point -> + point(point.position, point.color) + } + } + } + } + } + } + + companion object { + /** + * Creates a [Line] component - layout-based line representation + */ + @UIBuilder + fun Layout.line( + block: Line.() -> Unit = {} + ) = Line(this).apply(children::add).apply(block) + + /** + * Adds a [Line] behind given [layout] + */ + @UIBuilder + fun Layout.lineBehind( + layout: Layout, + block: Line.() -> Unit = {} + ) = Line(this).insertLayout(this, layout, false).apply(block) + + /** + * Adds a [Line] over given [layout] + */ + @UIBuilder + fun Layout.lineOver( + layout: Layout, + block: Line.() -> Unit = {} + ) = Line(this).insertLayout(this, layout, true).apply(block) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt b/common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt new file mode 100644 index 000000000..185e3c713 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2025 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.gui.component.core + +import com.lambda.gui.component.layout.Layout +import org.joml.Vector2d +import java.awt.Color + +abstract class LineLayout( + owner: Layout +) : Layout(owner) { + @UIRenderPr0p3rty var lineWidth = 1.0 + @UIRenderPr0p3rty var dashiness = 1.0 + @UIRenderPr0p3rty var dashPeriod = 1.0 + @UIRenderPr0p3rty var shade = false + + val points = mutableListOf() + + init { + properties.interactionPassthrough = true + } + + fun addPoint(position: Vector2d, color: Color = Color.WHITE) { + points.add(Point(position, color)) + } + + fun clearPoints() { + points.clear() + } + + data class Point( + val position: Vector2d, + val color: Color + ) +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index dc52b3bea..f6922c160 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -20,15 +20,33 @@ package com.lambda.module.modules.debug import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DynamicAABB.Companion.dynamicBox +import com.lambda.graphics.renderer.esp.builders.drawLine +import com.lambda.graphics.renderer.esp.builders.drawLineBetweenBoxes import com.lambda.graphics.renderer.esp.builders.ofBox +import com.lambda.graphics.renderer.gui.LineRenderer +import net.minecraft.util.math.Vec3d import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.setAlpha import com.lambda.util.world.entitySearch import net.minecraft.entity.LivingEntity import net.minecraft.util.math.Box +import org.joml.Vector2d import java.awt.Color +/** + * RenderTest module demonstrates different rendering techniques: + * 1. Drawing boxes around entities using DynamicESP + * 2. Drawing a box at the player's position using StaticESP + * 3. Drawing lines in 3D space: + * - Lines between entities + * - A 3D star shape around the player + * - A continuous line connecting multiple points + * 4. Drawing 2D lines on the screen (a triangle and a square) + * + * Enable the module and toggle "Draw Lines" to see the line examples. + * Adjust line width, dashiness, and dash period to see different line styles. + */ object RenderTest : Module( name = "Render:shrimp:Test:canned_food:", description = "RenderTest", @@ -41,19 +59,75 @@ object RenderTest : Module( private val test31 by setting("Holla huh 1", true, visibility = { !test1 }) private val test32 by setting("Holla buh 2", true, visibility = { !test1 }) + private val drawLines by setting("Draw Lines", true) + private val lineWidth by setting("Line Width", 2.0, 0.5..5.0, 0.5, visibility = ::drawLines) + private val dashiness by setting("Dashiness", 1.0, 0.0..1.0, 0.1, visibility = ::drawLines) + private val dashPeriod by setting("Dash Period", 1.0, 0.5..5.0, 0.5, visibility = ::drawLines) + private val outlineColor = Color(100, 150, 255).setAlpha(0.5) private val filledColor = outlineColor.setAlpha(0.2) + private val lineColor = Color(255, 100, 100).setAlpha(0.8) init { listen { - entitySearch(8.0) - .forEach { entity -> - it.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) + val entities = entitySearch(8.0).toList() + + // Draw boxes around entities + entities.forEach { entity -> + it.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) + } + + // Example of drawing lines between entities using the new drawLineBetweenBoxes function + if (drawLines && entities.size >= 2) { + for (i in 0 until entities.size - 1) { + val entity1 = entities[i] + val entity2 = entities[i + 1] + + it.renderer.drawLineBetweenBoxes( + entity1.dynamicBox, + entity2.dynamicBox, + lineColor, + lineWidth, + dashiness, + dashPeriod + ) } + } } listen { + // Draw a box at player position it.renderer.ofBox(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) + + if (drawLines) { + val renderer = it.renderer + val pos = player.pos + val size = 1.0 + + // Define the points of the star + val center = pos + val top = Vec3d(center.x, center.y + size, center.z) + val bottom = Vec3d(center.x, center.y - size, center.z) + val left = Vec3d(center.x - size, center.y, center.z) + val right = Vec3d(center.x + size, center.y, center.z) + val front = Vec3d(center.x, center.y, center.z + size) + val back = Vec3d(center.x, center.y, center.z - size) + + // Connect the points to form a 3D star using the new drawLine function + renderer.drawLine(top, left, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(top, right, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(top, front, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + renderer.drawLine(top, back, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + + renderer.drawLine(bottom, left, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(bottom, right, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(bottom, front, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + renderer.drawLine(bottom, back, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + + // Example of drawing a line connecting multiple points + val points = listOf(top, right, front, bottom, left, back, top) + renderer.drawLine(points, lineColor, lineWidth, dashiness, dashPeriod) + } } } } diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/line.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/line.glsl new file mode 100644 index 000000000..8c11d8c52 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/line.glsl @@ -0,0 +1,27 @@ +attributes { + vec4 pos; + float alpha; + vec4 color; +}; + +uniforms { + float u_Width; # fragment + float u_Dashiness; # fragment + float u_DashPeriod; # fragment +}; + +export { + vec4 v_Color; # color + float v_Alpha; # alpha +}; + +#define PI 3.1415926535 +#define SMOOTHING 100 + +void fragment() { + float smoothing = min(u_Width, SMOOTHING); + float dist = abs(v_Alpha - 0.5) * 2 * u_Width; + float alpha = smoothstep(u_Width, u_Width - smoothing, dist); + + color = v_Color * vec4(1.0, 1.0, 1.0, alpha); +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl new file mode 100644 index 000000000..6c1f007c5 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl @@ -0,0 +1,44 @@ +attributes { + vec4 pos; + float distance; + float width; + float alpha; + vec4 color; +}; + +uniforms { + float u_Length; # fragment + float u_Dashiness; # fragment + float u_DashPeriod; # fragment +}; + +export { + vec4 v_Color; # color + float v_Distance; # distance + float v_Width; # width + float v_Alpha; # alpha +}; + +#define PI 3.1415926535 +#define SMOOTHING 100 + +void fragment() { + float smoothing = min(v_Width, SMOOTHING); + float dist = abs(v_Alpha - 0.5) * 2 * v_Width; + float alpha = smoothstep(v_Width, v_Width - smoothing, dist); + + if (u_Length > 0.0) { + float startFade = smoothstep(0.0, smoothing * 0.5, v_Distance); + float endFade = smoothstep(u_Length, u_Length - smoothing * 0.5, v_Distance); + alpha *= min(startFade, endFade); + } + + if (u_Dashiness < 0.98) { + float dashSmoothing = smoothing / u_DashPeriod; + float dashPos = fract(v_Distance / u_DashPeriod); + float dashed = smoothstep(0.0, dashSmoothing, dashPos) * (1.0 - smoothstep(u_Dashiness, u_Dashiness + dashSmoothing, dashPos)); + alpha *= mix(dashed, 1.0, max(min((u_Dashiness - 0.9) * 10.0, 1.0), 0.0)); + } + + color = v_Color * vec4(1.0, 1.0, 1.0, alpha); +}# From c0d773384b7ad71b1d8221a09d56accc39ff6bad Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 10 May 2025 15:17:43 -0400 Subject: [PATCH 5/6] Chunked memcpr and misc --- .../graphics/buffer/vertex/VertexArray.kt | 6 +-- .../graphics/pipeline/PersistentBuffer.kt | 44 +++++++++++-------- .../graphics/pipeline/VertexPipeline.kt | 2 +- .../renderer/gui/font/CachedString.kt | 2 +- 4 files changed, 31 insertions(+), 23 deletions(-) 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 ca748e01a..a931f50fb 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 @@ -49,14 +49,14 @@ class VertexArray( private fun renderInternal( indicesSize: Long, indicesPointer: Long, - verticesOffset: Int + verticesOffset: Long ) = bind { glDrawElementsBaseVertex( vertexMode.mode, - indicesSize.toInt() / UInt.SIZE_BYTES, + indicesSize.toInt() / Int.SIZE_BYTES, GL_UNSIGNED_INT, indicesPointer, - verticesOffset / attributes.stride + verticesOffset.toInt() / attributes.stride, ) } diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt index 76c2aa471..351e6ee0a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt @@ -21,8 +21,8 @@ import com.lambda.graphics.buffer.Buffer.Companion.createPipelineBuffer import com.lambda.graphics.buffer.DynamicByteBuffer import com.lambda.graphics.buffer.DynamicByteBuffer.Companion.dynamicByteBuffer import com.lambda.graphics.gl.kibibyte +import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil.memCopy -import sun.misc.Unsafe /** * Represents a persistent dynamic coherent buffer for fast opengl rendering purposes @@ -47,7 +47,7 @@ class PersistentBuffer( val glBuffer = createPipelineBuffer(target) private var glSize = 0 - var uploadOffset = 0 + var uploadOffset = 0L fun upload() { val dataStart = byteBuffer.pointer + uploadOffset @@ -64,14 +64,14 @@ class PersistentBuffer( } if (snapshotData > 0 && snapshot.capacity >= byteBuffer.bytesPut) { - if (memcmp(snapshot, byteBuffer, uploadOffset, dataCount.toInt())) return + if (memcmp(snapshot, byteBuffer, uploadOffset, dataCount)) return } - glBuffer.update(uploadOffset.toLong(), dataCount, dataStart) + glBuffer.update(uploadOffset, dataCount, dataStart) } fun end() { - uploadOffset = byteBuffer.bytesPut.toInt() + uploadOffset = byteBuffer.bytesPut } fun sync() { @@ -91,21 +91,29 @@ class PersistentBuffer( fun use(block: () -> Unit) = glBuffer.bind { block() } - private fun memcmp(a: DynamicByteBuffer, b: DynamicByteBuffer, position: Int, size: Int): Boolean { - for (i in position..<(position + size)) { - if (UNSAFE.getByte(null, a.pointer + i) != - UNSAFE.getByte(null, b.pointer + i)) { - return false - } + private fun memcmp(a: DynamicByteBuffer, b: DynamicByteBuffer, position: Long, size: Long): Boolean { + if (a.capacity != b.capacity) return false + + val end = position + size + var head = position + + // Process the aligned bytes in chunks of 8 until we've reached the end + while (head + 8 <= end) { + val first = MemoryUtil.memGetLong(a.pointer + head) + val second = MemoryUtil.memGetLong(b.pointer + head) + if (first != second) return false + + head += 8 } - return true - } - companion object { - private val UNSAFE = run { - val unsafeField = Unsafe::class.java.getDeclaredField("theUnsafe") - unsafeField.setAccessible(true) - unsafeField.get(null) as Unsafe + while (head < end) { + val first = MemoryUtil.memGetByte(a.pointer + head) + val second = MemoryUtil.memGetByte(b.pointer + head) + if (first != second) return false + + head++ } + + return true } } diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt index 8347580dd..bccfd7fc4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt @@ -41,7 +41,7 @@ class VertexPipeline( private val vao = VertexArray(vertexMode, attributes) private val vbo = PersistentBuffer(GL_ARRAY_BUFFER, attributes.stride) - private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, UInt.SIZE_BYTES) + private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, Int.SIZE_BYTES) init { vao.linkVbo(vbo) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt index 09e6dd282..e7c8ca914 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt @@ -76,7 +76,7 @@ class CachedString( /* Create gl buffers and upload */ val vao = FontRenderer.vao vbo = PersistentBuffer(GL_ARRAY_BUFFER, vao.attributes.stride, builder.vertices.size) - ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, UInt.SIZE_BYTES, builder.indices.size) + ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, Int.SIZE_BYTES, builder.indices.size) vao.linkVbo(vbo) builder.uploadVertices(vbo.byteBuffer) From a08e64fa77fe4f134f6adf428d1ea4577ac5fcf5 Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 20 May 2025 03:47:32 +0200 Subject: [PATCH 6/6] Some cleanup --- .../graphics/renderer/esp/DynamicAABB.kt | 2 ++ .../esp/builders/DynamicESPLineBuilders.kt | 27 +++++----------- .../lambda/module/modules/debug/RenderTest.kt | 31 ++++++++----------- 3 files changed, 23 insertions(+), 37 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt index 540420854..d327c529a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt @@ -48,6 +48,8 @@ class DynamicAABB { return null } + fun center() = curr?.center ?: prev?.center ?: throw NullPointerException("No box is set") + companion object { val Entity.dynamicBox get() = DynamicAABB().apply { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt index 4cf589439..05ed8aac1 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt @@ -17,10 +17,12 @@ package com.lambda.graphics.renderer.esp.builders +import com.lambda.Lambda.mc import com.lambda.graphics.RenderMain import com.lambda.graphics.renderer.esp.DynamicAABB import com.lambda.graphics.renderer.esp.impl.DynamicESPRenderer import com.lambda.graphics.renderer.gui.LineRenderer +import com.lambda.util.math.minus import net.minecraft.util.math.Vec3d import java.awt.Color import org.joml.Vector2d @@ -121,22 +123,7 @@ fun DynamicESPRenderer.drawLineBetweenBoxes( dashiness: Double = 1.0, dashPeriod: Double = 1.0 ) { - val box1Pair = box1.getBoxPair() ?: return - val box2Pair = box2.getBoxPair() ?: return - - val center1 = Vec3d( - (box1Pair.first.minX + box1Pair.first.maxX) / 2, - (box1Pair.first.minY + box1Pair.first.maxY) / 2, - (box1Pair.first.minZ + box1Pair.first.maxZ) / 2 - ) - - val center2 = Vec3d( - (box2Pair.first.minX + box2Pair.first.maxX) / 2, - (box2Pair.first.minY + box2Pair.first.maxY) / 2, - (box2Pair.first.minZ + box2Pair.first.maxZ) / 2 - ) - - drawLine(center1, center2, color, color, width, dashiness, dashPeriod) + drawLine(box1.center(), box2.center(), color, color, width, dashiness, dashPeriod) } /** @@ -144,11 +131,13 @@ fun DynamicESPRenderer.drawLineBetweenBoxes( * Returns null if the point is behind the camera or outside the screen. */ private fun project3DTo2D(point: Vec3d): Vector2d? { + val transformedPoint = point - mc.gameRenderer.camera.pos + // Create a 4D vector from the 3D point val vec4 = Vector4f( - point.x.toFloat(), - point.y.toFloat(), - point.z.toFloat(), + transformedPoint.x.toFloat(), + transformedPoint.y.toFloat(), + transformedPoint.z.toFloat(), 1f ) diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index f6922c160..4723dfd50 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -69,29 +69,24 @@ object RenderTest : Module( private val lineColor = Color(255, 100, 100).setAlpha(0.8) init { - listen { - val entities = entitySearch(8.0).toList() + listen { event -> + val entities = entitySearch(8.0).sortedBy { it.distanceTo(player) } // Draw boxes around entities entities.forEach { entity -> - it.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) + event.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) } - // Example of drawing lines between entities using the new drawLineBetweenBoxes function - if (drawLines && entities.size >= 2) { - for (i in 0 until entities.size - 1) { - val entity1 = entities[i] - val entity2 = entities[i + 1] - - it.renderer.drawLineBetweenBoxes( - entity1.dynamicBox, - entity2.dynamicBox, - lineColor, - lineWidth, - dashiness, - dashPeriod - ) - } + entities.firstOrNull()?.let { first -> + // Example of drawing lines between entities using the new drawLineBetweenBoxes function + event.renderer.drawLineBetweenBoxes( + player.dynamicBox, + first.dynamicBox, + lineColor, + lineWidth, + dashiness, + dashPeriod + ) } }