diff --git a/build.gradle.kts b/build.gradle.kts index 62a7216c4..576788c9c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,8 @@ +import org.gradle.internal.jvm.* import net.fabricmc.loom.api.LoomGradleExtensionAPI +import org.apache.tools.ant.taskdefs.condition.Os import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.io.FileNotFoundException import java.util.* val modId: String by project @@ -66,6 +69,17 @@ subprojects { if (path == ":common") return@subprojects tasks { + register("renderDoc") { + val javaHome = Jvm.current().javaHome + val gradleWrapper = rootProject.tasks.wrapper.get().jarFile.absolutePath + + commandLine = listOf( + findExecutable("renderdoccmd") + ?: throw FileNotFoundException("Could not find the renderdoccmd executable"), + "capture", /* Remove the following 2 lines if you don't want api validation */ "--opt-api-validation", "--opt-api-validation-unmute", "--opt-hook-children", "--wait-for-exit", "--working-dir", ".", "$javaHome/bin/java", "-Xmx64m", "-Xms64m", /*"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005",*/ "-Dorg.gradle.appname=gradlew", "-Dorg.gradle.java.home=$javaHome", "-classpath", gradleWrapper, "org.gradle.wrapper.GradleWrapperMain", "${this@subprojects.path}:runClient", + ) + } + processResources { // Replaces placeholders in the mod info files filesMatching(targets) { @@ -118,3 +132,10 @@ allprojects { } } } + +private fun findExecutable(executable: String): String? { + val isWindows = Os.isFamily(Os.FAMILY_WINDOWS) + val cmd = if (isWindows) "where" else "which" + + return ProcessBuilder(cmd, executable).start().inputStream.bufferedReader().readText().trim().takeIf { it.isNotBlank() } +} diff --git a/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java b/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java index 2edcc7eca..84f0b9e21 100644 --- a/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.graphics.gl.VaoUtils; +import com.lambda.graphics.buffer.vertex.ElementBuffer; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gl.VertexBuffer; import net.minecraft.client.render.BufferBuilder; @@ -37,6 +37,6 @@ public class VertexBufferMixin { @Inject(method = "uploadIndexBuffer", at = @At("RETURN")) private void onConfigureIndexBuffer(BufferBuilder.DrawParameters parameters, ByteBuffer vertexBuffer, CallbackInfoReturnable cir) { RenderSystem.ShapeIndexBuffer value = cir.getReturnValue(); - VaoUtils.lastIbo = value == null ? this.indexBufferId : value.id; + ElementBuffer.lastIbo = value == null ? this.indexBufferId : value.id; } } diff --git a/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt b/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt index 19b9a9fff..f9a13eea9 100644 --- a/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt +++ b/common/src/main/kotlin/com/lambda/graphics/animation/Animation.kt @@ -18,8 +18,8 @@ package com.lambda.graphics.animation import com.lambda.Lambda.mc -import com.lambda.util.math.lerp import com.lambda.util.extension.partialTicks +import com.lambda.util.math.lerp import kotlin.math.abs import kotlin.reflect.KProperty diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt deleted file mode 100644 index 3786e53ac..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/BufferUsage.kt +++ /dev/null @@ -1,29 +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.graphics.buffer - -import com.lambda.graphics.gl.GLObject -import org.lwjgl.opengl.GL30C.GL_DYNAMIC_DRAW -import org.lwjgl.opengl.GL30C.GL_STATIC_DRAW -import org.lwjgl.opengl.GL30C.GL_STREAM_DRAW - -enum class BufferUsage(override val gl: Int) : GLObject { - STATIC(GL_STATIC_DRAW), - DYNAMIC(GL_DYNAMIC_DRAW), - STREAM(GL_STREAM_DRAW); -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt index 52112c156..dce732b33 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/FrameBuffer.kt @@ -19,9 +19,8 @@ package com.lambda.graphics.buffer import com.lambda.Lambda.mc import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.GlStateUtils.withBlendFunc import com.lambda.graphics.shader.Shader import com.lambda.graphics.texture.TextureUtils.bindTexture @@ -67,7 +66,7 @@ class FrameBuffer(private val depth: Boolean = false) { shader.use() shaderBlock(shader) - vao.use { + pipeline.use { grow(4) val uv1 = pos1 / RenderMain.screenSize @@ -82,9 +81,9 @@ class FrameBuffer(private val depth: Boolean = false) { } withBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) { - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } return this @@ -149,7 +148,7 @@ class FrameBuffer(private val depth: Boolean = false) { } companion object { - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private var lastFrameBuffer: Int? = null } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt new file mode 100644 index 000000000..9aaeb7a00 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IBuffer.kt @@ -0,0 +1,306 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.buffer + +import com.lambda.graphics.gl.bufferBound +import com.lambda.graphics.gl.bufferUsageValid +import com.lambda.graphics.gl.bufferValid +import org.lwjgl.opengl.GL30C.* +import org.lwjgl.opengl.GL44.glBufferStorage +import java.nio.ByteBuffer + +interface IBuffer { + /** + * Specifies how many buffer must be used + * + * | Number of Buffers | Purpose | + * |-------------------|---------------------------------------------------------------------------------------------------------------| + * | 1 Buffer | Simple operations like storing vertex data or a single texture. | + * | 2 Buffers | Double buffering for smooth rendering (alternating between writing to one buffer while reading from another). | + * | 3 Buffers | Triple buffering for improved frame rate and reduced screen tearing at the cost of memory usage. | + * + * In double buffering, you have a front buffer (A) and a back buffer (B). While one is displayed (A), the other (B) is being drawn to. After drawing, the buffers are swapped. However, during the swap (often synchronized with vertical retrace to avoid tearing), drawing cannot resume until it completes, potentially causing delays. + * + * In triple buffering, there is a front buffer (A) and two back buffers (B and C). This allows drawing to continue on the second back buffer (C) while waiting for the swap to finish between the front (A) and the first back buffer (B). This reduces idle time and improves frame rates, especially if the app runs slower than the monitor's refresh rate. + * + * Triple buffering helps maintain smoother frame rates, but if your app runs faster than the monitor's refresh rate, it offers little benefit as you eventually still wait for vblank synchronization. + */ + val buffers: Int + + /** + * Specifies how the buffers are used + * + * | Buffer Usage | Description | + * |--------------------------------|-----------------------------------------------------------------| + * | GL_STREAM_DRAW | Data is set once and used a few times for drawing. | + * | GL_STREAM_READ | Data is set once and used a few times for reading. | + * | GL_STREAM_COPY | Data is set once and used a few times for copying. | + * | GL_STATIC_DRAW | Data is set once and used many times for drawing. | + * | GL_STATIC_READ | Data is set once and used many times for reading. | + * | GL_STATIC_COPY | Data is set once and used many times for copying. | + * | GL_DYNAMIC_DRAW | Data is modified repeatedly and used many times for drawing. | + * | GL_DYNAMIC_READ | Data is modified repeatedly and used many times for reading. | + * | GL_DYNAMIC_COPY | Data is modified repeatedly and used many times for copying. | + */ + val usage: Int + + /** + * Specifies the target to which the buffer object is bound which must be one + * of the following: + * + * | Buffer Binding Target | Purpose | + * |-------------------------------|--------------------------------------| + * | GL_ARRAY_BUFFER | Vertex attributes | + * | GL_ATOMIC_COUNTER_BUFFER | Atomic counter storage | + * | GL_COPY_READ_BUFFER | Buffer copy source | + * | GL_COPY_WRITE_BUFFER | Buffer copy destination | + * | GL_DISPATCH_INDIRECT_BUFFER | Indirect compute dispatch commands | + * | GL_DRAW_INDIRECT_BUFFER | Indirect command arguments | + * | GL_ELEMENT_ARRAY_BUFFER | Vertex array indices | + * | GL_PIXEL_PACK_BUFFER | Pixel read target | + * | GL_PIXEL_UNPACK_BUFFER | Texture data source | + * | GL_QUERY_BUFFER | Query result buffer | + * | GL_SHADER_STORAGE_BUFFER | Read-write storage for shaders | + * | GL_TEXTURE_BUFFER | Texture data buffer | + * | GL_TRANSFORM_FEEDBACK_BUFFER | Transform feedback buffer | + * | GL_UNIFORM_BUFFER | Uniform block storage | + */ + val target: Int + + /** + * Specifies a combination of access flags indicating the desired + * access to the mapping range and must contain one or more of the following: + * + * | Flag | Description | Disclaimer | + * |-------------------------------|-----------------------------------------------------|---------------------------------------------------------------| + * | GL_MAP_READ_BIT | Allows reading buffer data. | Undefined if used without this flag. | + * | GL_MAP_WRITE_BIT | Allows modifying buffer data. | Undefined if used without this flag. | + * | GL_MAP_PERSISTENT_BIT | Enables persistent mapping during GL operations. | Requires proper allocation with GL_MAP_PERSISTENT_BIT. | + * | GL_MAP_COHERENT_BIT | Ensures changes are visible without extra steps. | Without this, explicit sync is needed. | + * | GL_MAP_INVALIDATE_RANGE_BIT | Discards previous contents of the mapped range. | Cannot be used with GL_MAP_READ_BIT. | + * | GL_MAP_INVALIDATE_BUFFER_BIT | Discards previous contents of the entire buffer. | Cannot be used with GL_MAP_READ_BIT. | + * | GL_MAP_FLUSH_EXPLICIT_BIT | Requires explicit flushing of modified sub-ranges. | Only with GL_MAP_WRITE_BIT. Data may be undefined if skipped. | + * | GL_MAP_UNSYNCHRONIZED_BIT | Skips synchronization before mapping. | May cause data corruption if regions overlap. | + */ + val access: Int + + /** + * Index of the current buffer + */ + var index: Int + + /** + * List of all the buffers + */ + val bufferIds: IntArray + + /** + * Binds the buffer id to the [target] + */ + fun bind(id: Int) = glBindBuffer(target, id) + + /** + * Binds current the buffer [index] to the [target] + */ + fun bind() = bind(bufferIds[index]) + + /** + * Swaps the buffer [index] if [buffers] is greater than 1 + */ + fun swap() { + index = (index + 1) % buffers + } + + /** + * Update the current buffer without re-allocating + * Alternative to [map] + */ + fun update( + data: ByteBuffer, + offset: Long, + ): Throwable? { + if (!bufferValid(target, access)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferBound(target)) + return IllegalArgumentException("Target is zero bound for glBufferSubData") + + glBufferSubData(target, offset, data) + + return null + } + + /** + * Allocates a region of memory for the buffer + * This function handles the buffer binding + * + * @param data The data to put in the new allocated buffer + */ + fun allocate(data: ByteBuffer): Throwable? { + if (!bufferValid(target, access)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferData(target, data, usage) + bind(0) + + return null + } + + /** + * Grows the backing buffers + * This function handles the buffer binding + * + * @param size The size of the new buffer + */ + fun allocate(size: Long): Throwable? { + if (!bufferValid(target, access)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferData(target, size.coerceAtLeast(0), usage) + bind(0) + + return null + } + + /** + * Create a new buffer storage + * This function cannot be called twice for the same buffer + * This function handles the buffer binding + */ + fun storage(data: ByteBuffer): Throwable? { + if (!bufferValid(target, access)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferStorage(target, data, usage) + bind(0) + + return null + } + + /** + * Create a new buffer storage + * This function cannot be called twice for the same buffer + * This function handles the buffer binding + * + * @param size The size of the storage buffer + */ + fun storage(size: Long): Throwable? { + if (!bufferValid(target, access)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferUsageValid(usage)) + return IllegalArgumentException("Buffer usage is invalid") + + bind() + glBufferStorage(target, size.coerceAtLeast(0), usage) + bind(0) + + return null + } + + /** + * Maps all or part of a buffer object's data store into the client's address space + * + * @param offset Specifies the starting offset within the buffer of the range to be mapped. + * @param size Specifies the length of the range to be mapped. + * @param block Lambda scope with the mapped buffer passed in + * @return Error encountered during the mapping process + */ + fun map( + offset: Long, + size: Long, + block: (ByteBuffer) -> Unit + ): Throwable? { + if ( + offset < 0 || + size < 0 + ) return IllegalArgumentException("Invalid offset or size parameter offset: $offset size: $size") + + if (!bufferValid(target, access)) + return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") + + if (!bufferBound(target)) + return IllegalArgumentException("Target is zero bound for glMapBufferRange") + + if ( + offset + size > glGetBufferParameteri(target, GL_BUFFER_SIZE) + ) return IllegalArgumentException("Out of bound mapping: $offset + $size > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") + + if ( + glGetBufferParameteri(target, GL_BUFFER_MAPPED) + == GL_TRUE + ) return IllegalStateException("Buffer is already mapped, something wrong happened") + + if ( + access and GL_MAP_WRITE_BIT == 0 && + access and GL_MAP_READ_BIT == 0 + ) return IllegalArgumentException("Neither GL_MAP_READ_BIT nor GL_MAP_WRITE_BIT is set") + + if ( + access and GL_MAP_READ_BIT != 0 && + (access and GL_MAP_INVALIDATE_RANGE_BIT == 0 || + access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || + access and GL_MAP_UNSYNCHRONIZED_BIT == 0 + ) + ) return IllegalArgumentException("GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set.") + + // Map the buffer into the client's memory + val sharedRegion = glMapBufferRange(target, offset, size, access) + ?: return IllegalStateException("Failed to map buffer") + + // Update data on the shared buffer + block(sharedRegion) + + // Release the buffer + if (!glUnmapBuffer(target)) + return IllegalStateException("An unknown error occurred due to GPU memory availability of buffer corruption") + + return null + } + + /** + * Sets the given data into the client mapped memory and executes the provided processing function to manage data transfer. + * + * @param data Data to set in memory + * @param offset The starting offset within the buffer of the range to be mapped + * @return Error encountered during the mapping process + */ + fun upload(data: ByteArray, offset: Long): Throwable? = + upload(ByteBuffer.wrap(data), offset) + + /** + * Sets the given data into the client mapped memory and executes the provided processing function to manage data transfer. + * + * @param data Data to set in memory + * @param offset The starting offset within the buffer of the range to be mapped + * @return Error encountered during the mapping process + */ + fun upload(data: ByteBuffer, offset: Long): Throwable? +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/IRenderContext.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt similarity index 97% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/IRenderContext.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt index efa9de588..3fb28a33b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/IRenderContext.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.buffer.vao +package com.lambda.graphics.buffer import java.awt.Color diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt new file mode 100644 index 000000000..ef886c56b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt @@ -0,0 +1,207 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.buffer + +import com.lambda.Lambda.LOG +import com.lambda.graphics.buffer.vertex.ElementBuffer +import com.lambda.graphics.buffer.vertex.VertexArray +import com.lambda.graphics.buffer.vertex.VertexBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.Matrices +import com.lambda.graphics.gl.Memory +import com.lambda.graphics.gl.Memory.address +import com.lambda.graphics.gl.Memory.byteBuffer +import com.lambda.graphics.gl.Memory.int +import com.lambda.graphics.gl.Memory.vector2f +import com.lambda.graphics.gl.Memory.vector3f +import com.lambda.graphics.gl.kibibyte +import org.joml.Vector4d +import org.lwjgl.opengl.GL20C.* +import java.awt.Color + +class VertexPipeline( + private val mode: VertexMode, + attributes: VertexAttrib.Group, +) : IRenderContext { + private val stride = attributes.stride + private val size = stride * mode.indicesCount + + private val vao = VertexArray() + private val vbo = VertexBuffer(mode, attributes) + private val ebo = ElementBuffer(mode) + + private var vertices = byteBuffer(size * 1.kibibyte) + private var verticesPointer = address(vertices) + private var verticesPosition = verticesPointer + + private var indices = byteBuffer(mode.indicesCount * 2.kibibyte) + private var indicesPointer = address(indices) + private var indicesCount = 0 + private var uploadedIndices = 0 + + private var vertexIndex = 0 + + override fun vec3(x: Double, y: Double, z: Double): VertexPipeline { + verticesPosition += vector3f(verticesPosition, x, y, z) + return this + } + + override fun vec2(x: Double, y: Double): VertexPipeline { + verticesPosition += vector2f(verticesPosition, x, y) + return this + } + + override fun vec3m(x: Double, y: Double, z: Double): IRenderContext { + Matrices.vertexTransformer?.let { mat -> + val vec = Vector4d(x, y, z, 1.0).apply(mat::transform) + vec3(vec.x, vec.y, vec.z) + } ?: vec3(x, y, z) + + return this + } + + override fun vec2m(x: Double, y: Double): IRenderContext { + Matrices.vertexTransformer?.let { mat -> + val vec = Vector4d(x, y, 0.0, 1.0).apply(mat::transform) + vec2(vec.x, vec.y) + } ?: vec2(x, y) + return this + } + + override fun float(v: Double): VertexPipeline { + verticesPosition += Memory.float(verticesPosition, v) + return this + } + + override fun color(color: Color): VertexPipeline { + verticesPosition += Memory.color(verticesPosition, color) + return this + } + + override fun end() = vertexIndex++ + + override fun putLine(vertex1: Int, vertex2: Int) { + growIndices(2) + val p = indicesPointer + indicesCount * 4L + + int(p + 0, vertex1) + int(p + 4, vertex2) + indicesCount += 2 + } + + override fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) { + growIndices(3) + val position = indicesPointer + indicesCount * 4L + + int(position + 0, vertex1) + int(position + 4, vertex2) + int(position + 8, vertex3) + indicesCount += 3 + } + + override fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) { + growIndices(6) + val position = indicesPointer + indicesCount * 4L + + int(position + 0, vertex1) + int(position + 4, vertex2) + int(position + 8, vertex3) + int(position + 12, vertex3) + int(position + 16, vertex4) + int(position + 20, vertex1) + indicesCount += 6 + } + + override fun grow(amount: Int) { + val requiredCapacity = (vertexIndex + amount + 1) * size + if (requiredCapacity < vertices.capacity()) return + + val offset = verticesPosition - verticesPointer + val newSize = vertices.capacity().let { it * 2 + it % size } + + val newVertices = byteBuffer(newSize) + Memory.copy(address(vertices), address(newVertices), offset) + + vertices = newVertices + verticesPointer = address(vertices) + verticesPosition = verticesPointer + offset + } + + private fun growIndices(amount: Int) { + val requiredCapacity = (indicesCount + amount) * 4 + if (requiredCapacity < indices.capacity()) return + + val newSize = indices.capacity().let { it * 2 + it % (mode.indicesCount * 4) } + val newIndices = byteBuffer(newSize) + + Memory.copy(address(indices), address(newIndices), indicesCount * 4L) + + indices = newIndices + indicesPointer = address(indices) + } + + override fun render() { + if (uploadedIndices <= 0) return + + vao.bind() + glDrawElements(mode.gl, uploadedIndices, GL_UNSIGNED_INT, 0) + vao.bind(0) + } + + override fun upload() { + if (indicesCount <= 0) return + + val vboData = vertices.limit((verticesPosition - verticesPointer).toInt()) + val eboData = indices.limit(indicesCount * 4) + + vbo.upload(vboData, 0)?.let(LOG::error) + ebo.upload(eboData, 0)?.let(LOG::error) + + uploadedIndices = indicesCount + } + + override fun clear() { + verticesPosition = verticesPointer + vertexIndex = 0 + indicesCount = 0 + uploadedIndices = 0 + } + + init { + // All the buffers have been generated, all we have to + // do now it bind them correctly and populate them + vao.bind() + vbo.bind() + ebo.bind() + + // Populate the buffers + attributes.attributes + .foldIndexed(0L) { index, pointer, attrib -> + glEnableVertexAttribArray(index) + glVertexAttribPointer(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) + + pointer + attrib.size // I'm not sure why there's no accumulator indexed iterator, this cost me many hours + } + + // Unbind everything to avoid accidental modification + vao.bind(0) + vbo.bind(0) + ebo.bind(0) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt deleted file mode 100644 index e0f1204f6..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pbo/PixelBuffer.kt +++ /dev/null @@ -1,186 +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.graphics.buffer.pbo - -import com.lambda.Lambda.LOG -import com.lambda.graphics.buffer.BufferUsage -import org.lwjgl.opengl.GL -import org.lwjgl.opengl.GL45C.* -import java.nio.ByteBuffer - -/** - * Represents a Pixel Buffer Object (PBO) that facilitates asynchronous data transfer to the GPU. - * This class manages the creation, usage, and cleanup of PBOs and provides methods to map textures and upload data efficiently. - * - * @property width The width of the texture in pixels. - * @property height The height of the texture in pixels. - * @property buffers The number of PBOs to be used. Default is 2, which allows double buffering. - * @property bufferUsage The usage pattern of the buffer, indicating how the buffer will be used (static, dynamic, etc.). - */ -class PixelBuffer( - private val width: Int, - private val height: Int, - private val buffers: Int = 2, - private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC, -) { - private val pboIds = IntArray(buffers) - private var writeIdx = 0 // Used to copy pixels from the PBO to the texture - private var uploadIdx = 0 // Used to upload data to the PBO - - private val queryId = glGenQueries() // Used to measure the time taken to upload data to the PBO - private val uploadTime - get() = IntArray(1).also { glGetQueryObjectiv(queryId, GL_QUERY_RESULT, it) }.first() - private var transferRate = 0L // The transfer rate in bytes per second - - private val pboSupported = GL.getCapabilities().OpenGL30 || GL.getCapabilities().GL_ARB_pixel_buffer_object - - private var initialDataSent: Boolean = false - - /** - * Maps the given texture ID to the buffer and performs the necessary operations to upload the texture data. - * - * @param id The texture ID to which the buffer will be mapped. - * @param buffer The [ByteBuffer] containing the pixel data to be uploaded to the texture. - */ - fun mapTexture(id: Int, buffer: ByteBuffer) { - if (!initialDataSent) { - glBindTexture(GL_TEXTURE_2D, id) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) - glBindTexture(GL_TEXTURE_2D, 0) - - initialDataSent = true - - return - } - - upload(buffer) { - // Bind the texture - glBindTexture(GL_TEXTURE_2D, id) - - if (buffers > 0 && pboSupported) { - // Bind the next PBO to update pixel values - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[writeIdx]) - - // Perform the actual data transfer to the GPU - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0) - } else { - // Perform the actual data transfer to the GPU - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer) - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - - // Unbind the texture - glBindTexture(GL_TEXTURE_2D, 0) - } - } - - /** - * Uploads the given pixel data to the PBO and executes the provided processing function to manage the PBO's data transfer. - * - * @param data The [ByteBuffer] containing the pixel data to be uploaded. - * @param process A lambda function to execute after uploading the data to manage the PBO's data transfer. - */ - fun upload(data: ByteBuffer, process: () -> Unit) = - recordTransfer { - if (buffers >= 2) - uploadIdx = (writeIdx + 1) % buffers - - // Copy the pixel values from the PBO to the texture - process() - - // Bind the current PBO for writing - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[uploadIdx]) - - // Note that glMapBuffer() causes sync issue. - // If GPU is working with this buffer, glMapBuffer() will wait(stall) - // until GPU to finish its job. To avoid waiting (idle), you can call - // first glBufferData() with NULL pointer before glMapBuffer(). - // If you do that, the previous data in PBO will be discarded and - // glMapBuffer() returns a new allocated pointer immediately - // even if GPU is still working with the previous data. - glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl) - - // Map the buffer into the memory - val bufferData = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY) - if (bufferData != null) { - bufferData.put(data) - - // Release the buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) - } else throw IllegalStateException("Failed to map the buffer") - - // Unbind the PBO - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) - - // Swap the indices - writeIdx = uploadIdx - } - - /** - * Measures and records the time taken to transfer data to the PBO, calculating the transfer rate in bytes per second. - * - * @param block A lambda function representing the block of code where the transfer occurs. - */ - private fun recordTransfer(block: () -> Unit) { - // Start the timer - glBeginQuery(GL_TIME_ELAPSED, queryId) - - // Perform the transfer - block() - - // Stop the timer - glEndQuery(GL_TIME_ELAPSED) - - // Calculate the transfer rate - val time = uploadTime - if (time > 0) transferRate = (width * height * 4L * 1_000_000_000) / time - } - - /** - * Cleans up resources by deleting the PBOs when the object is no longer in use. - */ - fun finalize() { - // Delete the PBOs - glDeleteBuffers(pboIds) - } - - /** - * Initializes the PBOs, allocates memory for them, and handles unsupported PBO scenarios. - * - * @throws IllegalArgumentException If the number of buffers is less than 0. - */ - init { - if (buffers < 0) throw IllegalArgumentException("Buffers must be greater than or equal to 0") - - if (!pboSupported && buffers > 0) - LOG.warn("Client tried to utilize PBOs, but they are not supported on the machine, falling back to direct buffer upload") - - // Generate the PBOs - glGenBuffers(pboIds) - - // Fill the buffers with null data to allocate the memory spaces - repeat(buffers) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[it]) - glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * 4L, bufferUsage.gl) - } - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) // Unbind the buffer - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt new file mode 100644 index 000000000..ef8f7495d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.buffer.pixel + +import com.lambda.graphics.buffer.IBuffer +import com.lambda.graphics.gl.padding +import com.lambda.graphics.gl.putTo +import com.lambda.graphics.texture.Texture +import org.lwjgl.opengl.GL45C.* +import java.nio.ByteBuffer + +/** + * Represents a Pixel Buffer Object (PBO) that facilitates asynchronous data transfer to the GPU. + * This class manages the creation, usage, and cleanup of PBOs and provides methods to upload (map) data efficiently. + * + * **Process**: + * Every function that performs a pixel transfer operation can use buffer objects instead of client memory. + * Functions that perform an upload operation, a pixel unpack, will use the buffer object bound to the target GL_PIXEL_UNPACK_BUFFER. + * If a buffer is bound, then the pointer value that those functions take is not a pointer, but an offset from the beginning of that buffer. + * + * @property width The width of the texture + * @property height The height of the texture + * @property texture The [Texture] instance + * @property format The image format that will be uploaded + * + * @see Pixel Buffer Object + */ +class PixelBuffer( + private val width: Int, + private val height: Int, + private val texture: Texture, + private val format: Int, +) : IBuffer { + override val buffers: Int = 2 + override val usage: Int = GL_STATIC_DRAW + override val target: Int = GL_PIXEL_UNPACK_BUFFER + override val access: Int = GL_MAP_WRITE_BIT or GL_MAP_COHERENT_BIT + override var index = 0 + override val bufferIds = IntArray(buffers).apply { glGenBuffers(this) } + + private val channels = channelMapping[format] ?: throw IllegalArgumentException("Image format unsupported") + private val internalFormat = reverseChannelMapping[channels] ?: throw IllegalArgumentException("Image internal format unsupported") + private val size = width * height * channels * 1L + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? { + // Bind PBO to unpack the data into the texture + bind() + + // Bind the texture and PBO + glBindTexture(GL_TEXTURE_2D, texture.id) + + // Copy pixels from PBO to texture object + // Use offset instead of pointer + glTexSubImage2D( + GL_TEXTURE_2D, // Target + 0, // Mipmap level + 0, 0, // x and y offset + width, height, // width and height of the texture (set to your size) + format, // Format (depends on your data) + GL_UNSIGNED_BYTE, // Type (depends on your data) + 0, // PBO offset (for asynchronous transfer) + ) + + // Unbind the texture + glBindTexture(GL_TEXTURE_2D, 0) + + // Swap the buffer + swap() + + // Bind PBO to update pixel source + bind() + + // Map the buffer into the client's memory + val error = map(offset, size, data::putTo) + + // Unbind + bind(0) + + return error + } + + init { + // Bind the texture + glBindTexture(GL_TEXTURE_2D, texture.id) + + // Calculate memory padding in the case we are using tightly + // packed data in order to save memory and satisfy the computer's + // architecture memory alignment + // https://en.wikipedia.org/wiki/Data_structure_alignment + // In this case we calculate the padding and subtract this to 4 + // in order to tell the padding size + glPixelStorei(GL_UNPACK_ALIGNMENT, 4 - padding(channels)) + + // Allocate texture storage + // TODO: Might want to figure out the data type based on the input + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, 0) + + // Set the texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + + // Unbind the texture + glBindTexture(GL_TEXTURE_2D, 0) + + // Fill the storage with null + storage(size) + } + + companion object { + /** + * Returns how many channels are used for each image format + */ + private val channelMapping = mapOf( + GL_RED to 1, + GL_GREEN to 1, + GL_BLUE to 1, + GL_ALPHA to 1, + GL_RG to 2, + GL_RGB to 3, + GL_BGR to 3, + GL_RGBA to 4, + GL_BGRA to 4, + ) + + /** + * Returns an internal format based on how many channels there are + */ + private val reverseChannelMapping = mapOf( + 1 to GL_RED, + 2 to GL_RG, + 3 to GL_RGB, + 4 to GL_RGBA, + ) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt deleted file mode 100644 index 4bec57e98..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt +++ /dev/null @@ -1,244 +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.graphics.buffer.vao - -import com.lambda.graphics.buffer.BufferUsage -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode -import com.lambda.graphics.gl.Matrices -import com.lambda.graphics.gl.Memory.address -import com.lambda.graphics.gl.Memory.byteBuffer -import com.lambda.graphics.gl.Memory.capacity -import com.lambda.graphics.gl.Memory.color -import com.lambda.graphics.gl.Memory.copy -import com.lambda.graphics.gl.Memory.float -import com.lambda.graphics.gl.Memory.int -import com.lambda.graphics.gl.Memory.vec2 -import com.lambda.graphics.gl.Memory.vec3 -import com.lambda.graphics.gl.VaoUtils -import com.lambda.graphics.gl.VaoUtils.bindIndexBuffer -import com.lambda.graphics.gl.VaoUtils.bindVertexArray -import com.lambda.graphics.gl.VaoUtils.bindVertexBuffer -import com.lambda.graphics.gl.VaoUtils.bufferData -import com.lambda.graphics.gl.VaoUtils.unbindIndexBuffer -import com.lambda.graphics.gl.VaoUtils.unbindVertexArray -import com.lambda.graphics.gl.VaoUtils.unbindVertexBuffer -import com.lambda.threading.runGameScheduled -import org.joml.* -import org.lwjgl.opengl.GL30C.* -import java.awt.Color -import java.nio.ByteBuffer - -class VAO( - private val vertexMode: VertexMode, - attribGroup: VertexAttrib.Group, - private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC -) : IRenderContext { - private var vao = 0 - private var vbo = 0 - private var ibo = 0 - - private val objectSize: Int - - private var vertices: ByteBuffer - private var verticesPointer: Long - private var verticesPosition: Long - - private var indices: ByteBuffer - private var indicesPointer: Long - private var indicesCount = 0 - private var uploadedIndices = 0 - - private var vertexIndex = 0 - - init { - val stride = attribGroup.stride - objectSize = stride * vertexMode.indicesCount - - vertices = byteBuffer(objectSize * 256 * 4) - verticesPointer = address(vertices) - verticesPosition = verticesPointer - indices = byteBuffer(vertexMode.indicesCount * 512 * 4) - indicesPointer = address(indices) - - vao = glGenVertexArrays() - bindVertexArray(vao) - - vbo = glGenBuffers() - bindVertexBuffer(vbo) - - ibo = glGenBuffers() - bindIndexBuffer(ibo) - - var pointer = 0L - attribGroup.attributes.forEachIndexed { index, attrib -> - VaoUtils.enableVertexAttribute(index) - VaoUtils.vertexAttribute(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) - pointer += attrib.size - } - - unbindVertexArray() - unbindVertexBuffer() - unbindIndexBuffer() - } - - override fun vec3(x: Double, y: Double, z: Double): VAO { - verticesPosition += vec3(verticesPosition, x, y, z) - return this - } - - override fun vec2(x: Double, y: Double): VAO { - verticesPosition += vec2(verticesPosition, x, y) - return this - } - - override fun vec3m(x: Double, y: Double, z: Double): IRenderContext { - Matrices.vertexTransformer?.let { mat -> - val vec = Vector4d(x, y, z, 1.0).apply(mat::transform) - vec3(vec.x, vec.y, vec.z) - } ?: vec3(x, y, z) - - return this - } - - override fun vec2m(x: Double, y: Double): IRenderContext { - Matrices.vertexTransformer?.let { mat -> - val vec = Vector4d(x, y, 0.0, 1.0).apply(mat::transform) - vec2(vec.x, vec.y) - } ?: vec2(x, y) - return this - } - - override fun float(v: Double): VAO { - verticesPosition += float(verticesPosition, v) - return this - } - - override fun color(color: Color): VAO { - verticesPosition += color(verticesPosition, color) - return this - } - - override fun end() = vertexIndex++ - - override fun putLine(vertex1: Int, vertex2: Int) { - growIndices(2) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - indicesCount += 2 - } - - override fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) { - growIndices(3) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - int(p + 8, vertex3) - indicesCount += 3 - } - - override fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) { - growIndices(6) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - int(p + 8, vertex3) - int(p + 12, vertex3) - int(p + 16, vertex4) - int(p + 20, vertex1) - indicesCount += 6 - } - - override fun grow(amount: Int) { - val cap = vertices.capacity - if ((vertexIndex + amount + 1) * objectSize < cap) return - - val offset = verticesPosition - verticesPointer - var newSize = cap * 2 - if (newSize % objectSize != 0) newSize += newSize % objectSize - val newVertices = byteBuffer(newSize) - - val from = address(vertices) - val to = address(newVertices) - copy(from, to, offset) - - vertices = newVertices - verticesPointer = address(vertices) - verticesPosition = verticesPointer + offset - } - - private fun growIndices(amount: Int) { - val cap = indices.capacity - if ((indicesCount + amount) * 4 < cap) return - - var newSize = cap * 2 - if (newSize % vertexMode.indicesCount != 0) newSize += newSize % (vertexMode.indicesCount * 4) - val newIndices = byteBuffer(newSize) - - val from = address(indices) - val to = address(newIndices) - copy(from, to, indicesCount * 4L) - - indices = newIndices - indicesPointer = address(indices) - } - - override fun render() { - if (uploadedIndices <= 0) return - - bindVertexArray(vao) - glDrawElements(vertexMode.gl, uploadedIndices, GL_UNSIGNED_INT, 0) - unbindVertexArray() - } - - override fun upload() { - if (indicesCount <= 0) return - - val vboData = vertices.limit((verticesPosition - verticesPointer).toInt()) - val iboData = indices.limit(indicesCount * 4) - - bindVertexBuffer(vbo) - bufferData(GL_ARRAY_BUFFER, vboData, bufferUsage.gl) - unbindVertexBuffer() - - bindIndexBuffer(ibo) - bufferData(GL_ELEMENT_ARRAY_BUFFER, iboData, bufferUsage.gl) - unbindIndexBuffer() - - uploadedIndices = indicesCount - } - - override fun clear() { - verticesPosition = verticesPointer - vertexIndex = 0 - indicesCount = 0 - uploadedIndices = 0 - } - - protected fun finalize() { - runGameScheduled { - glDeleteBuffers(ibo) - glDeleteBuffers(vbo) - glDeleteVertexArrays(vao) - } - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt new file mode 100644 index 000000000..20cc67c34 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.buffer.vertex + +import com.lambda.graphics.buffer.IBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.kibibyte +import org.lwjgl.opengl.GL30C.* +import java.nio.ByteBuffer + +class ElementBuffer(mode: VertexMode) : IBuffer { + override val buffers: Int = 1 + override val usage: Int = GL_DYNAMIC_DRAW + override val target: Int = GL_ELEMENT_ARRAY_BUFFER + override val access: Int = GL_MAP_WRITE_BIT + override var index = 0 + override val bufferIds = intArrayOf(glGenBuffers()) + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? = allocate(data) + + override fun bind(id: Int) { + if (id != 0) prevIbo = lastIbo + val target = if (id != 0) id else prevIbo + + super.bind(target) + } + + init { + allocate(mode.indicesCount * 2L.kibibyte) + } + + companion object { + @JvmField + var lastIbo = 0 + var prevIbo = 0 + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt new file mode 100644 index 000000000..1043f98c9 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.buffer.vertex + +import com.lambda.graphics.buffer.IBuffer +import net.minecraft.client.render.BufferRenderer +import org.lwjgl.opengl.GL30C.* +import java.nio.ByteBuffer + +class VertexArray : IBuffer { + override val buffers: Int = 1 + override val usage: Int = -1 + override val target: Int = -1 + override val access: Int = -1 + override var index = 0 + override val bufferIds = intArrayOf(glGenVertexArrays()) + + override fun map( + offset: Long, + size: Long, + block: (ByteBuffer) -> Unit + ): Throwable? = throw UnsupportedOperationException("Cannot map a vertex array object to memory") + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? = throw UnsupportedOperationException("Data cannot be uploaded to a vertex array object") + + override fun allocate(size: Long) = throw UnsupportedOperationException("Cannot grow a vertex array object") + + override fun bind(id: Int) { + glBindVertexArray(id); BufferRenderer.currentVertexBuffer = null + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt new file mode 100644 index 000000000..91977535d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.buffer.vertex + +import com.lambda.graphics.buffer.IBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.kibibyte +import com.lambda.graphics.gl.putTo +import org.lwjgl.opengl.GL30C.* +import java.nio.ByteBuffer + +class VertexBuffer( + mode: VertexMode, + attributes: VertexAttrib.Group, +) : IBuffer { + override val buffers: Int = 1 + override val usage: Int = GL_DYNAMIC_DRAW + override val target: Int = GL_ARRAY_BUFFER + override val access: Int = GL_MAP_WRITE_BIT + override var index = 0 + override val bufferIds = intArrayOf(glGenBuffers()) + + override fun upload( + data: ByteBuffer, + offset: Long, + ): Throwable? = allocate(data) + + init { + allocate(attributes.stride * mode.indicesCount * 1L.kibibyte) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexAttrib.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt similarity index 97% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexAttrib.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt index 2e5367735..729334ac7 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexAttrib.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.buffer.vao.vertex +package com.lambda.graphics.buffer.vertex.attributes import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_FLOAT diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexMode.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt similarity index 94% rename from common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexMode.kt rename to common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt index 886e9b502..e0c6c45df 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/VertexMode.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.buffer.vao.vertex +package com.lambda.graphics.buffer.vertex.attributes import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_LINES diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt new file mode 100644 index 000000000..c1a1636c9 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.gl + +import org.lwjgl.opengl.GL44.* + +/** + * Map of valid buffer binding target to their respective binding check parameter + * using [bufferBound] + */ +val bindingCheckMappings = mapOf( + GL_ARRAY_BUFFER to GL_ARRAY_BUFFER_BINDING, + GL_ATOMIC_COUNTER_BUFFER to GL_ATOMIC_COUNTER_BUFFER_BINDING, + GL_COPY_READ_BUFFER_BINDING to GL_COPY_READ_BUFFER_BINDING, + GL_COPY_WRITE_BUFFER_BINDING to GL_COPY_WRITE_BUFFER_BINDING, + GL_DISPATCH_INDIRECT_BUFFER to GL_DISPATCH_INDIRECT_BUFFER_BINDING, + GL_DRAW_INDIRECT_BUFFER to GL_DRAW_INDIRECT_BUFFER_BINDING, + GL_ELEMENT_ARRAY_BUFFER to GL_ELEMENT_ARRAY_BUFFER_BINDING, + GL_PIXEL_PACK_BUFFER to GL_PIXEL_PACK_BUFFER_BINDING, + GL_PIXEL_UNPACK_BUFFER to GL_PIXEL_UNPACK_BUFFER_BINDING, + GL_QUERY_BUFFER to GL_QUERY_BUFFER_BINDING, + GL_SHADER_STORAGE_BUFFER to GL_SHADER_STORAGE_BUFFER_BINDING, + GL_TEXTURE_BUFFER to GL_TEXTURE_BUFFER_BINDING, + GL_TRANSFORM_FEEDBACK_BUFFER to GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, + GL_UNIFORM_BUFFER to GL_UNIFORM_BUFFER_BINDING, +) + +/** + * Returns whether the buffer target is valid + */ +fun bufferValid(target: Int, access: Int): Boolean = + target in bindingCheckMappings && + // If access contains GL_MAP_COHERENT_BIT, it must also contain GL_MAP_PERSISTENT_BIT. + (access and GL_MAP_COHERENT_BIT == 0 || access and GL_MAP_PERSISTENT_BIT != 0) && + // If access contains GL_MAP_PERSISTENT_BIT, it must also contain at least one of GL_MAP_READ_BIT or GL_MAP_WRITE_BIT. + (access and GL_MAP_PERSISTENT_BIT == 0 || (access and (GL_MAP_READ_BIT or GL_MAP_WRITE_BIT) != 0)) + +/** + * Returns whether the provided buffer target is bound + */ +fun bufferBound(target: Int): Boolean = + IntArray(1) + .apply { glGetIntegerv(bindingCheckMappings.getValue(target), this) }[0] != GL_FALSE + +/** + * Returns whether the provided buffer usage is valid + */ +fun bufferUsageValid(usage: Int) = usage >= GL_STREAM_DRAW && usage <= GL_DYNAMIC_DRAW diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt index 77f4a3152..8b1ec9708 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt @@ -17,64 +17,116 @@ package com.lambda.graphics.gl -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import org.lwjgl.system.MemoryUtil.memPutByte +import org.lwjgl.system.MemoryUtil.memPutFloat +import org.lwjgl.system.MemoryUtil.memPutInt import java.awt.Color import java.nio.Buffer import java.nio.ByteBuffer object Memory { - private val floatSize = VertexAttrib.Float.size - private val vec2Size = VertexAttrib.Vec2.size - private val vec3Size = VertexAttrib.Vec3.size - private val colorSize = VertexAttrib.Color.size - - fun vec2(address: Long, x: Double, y: Double): Int { + /** + * Puts a float for each axis at the current buffer position + */ + fun vector2f(address: Long, x: Double, y: Double): Int { float(address + 0, x) float(address + 4, y) - return vec2Size + return 8 } - fun vec3(address: Long, x: Double, y: Double, z: Double): Int { + /** + * Puts a float for each axis at the current buffer position + */ + fun vector3f(address: Long, x: Double, y: Double, z: Double): Int { float(address + 0, x) float(address + 4, y) float(address + 8, z) - return vec3Size + return 12 } + /** + * Puts a byte for each color channel at the current buffer position + */ fun color(address: Long, color: Color): Int { byte(address + 0, color.red.toByte()) byte(address + 1, color.green.toByte()) byte(address + 2, color.blue.toByte()) byte(address + 3, color.alpha.toByte()) - return colorSize + return 4 } - private fun byte(address: Long, value: Byte) { - MemoryUtil.memPutByte(address, value) + /** + * Puts a byte at the current buffer position + */ + fun byte(address: Long, value: Byte): Int { + memPutByte(address, value) + return 1 } - fun int(address: Long, value: Int) { - MemoryUtil.memPutInt(address, value) + /** + * Puts an integer at the current buffer position + */ + fun int(address: Long, value: Int): Int { + memPutInt(address, value) + return 4 } + /** + * Puts a float at the current buffer position + */ fun float(address: Long, value: Double): Int { - MemoryUtil.memPutFloat(address, value.toFloat()) - return floatSize + memPutFloat(address, value.toFloat()) + return 4 } - fun address(buffer: Buffer): Long { - return MemoryUtil.memAddress0(buffer) - } + /** + * Returns the address of the first element within the buffer + */ + fun address(buffer: Buffer): Long = MemoryUtil.memAddress0(buffer) - fun copy(from: Long, to: Long, bytes: Long) { - MemoryUtil.memCopy(from, to, bytes) - } + /** + * Copies [bytes] bytes from [src] to [dst] + */ + fun copy(src: Long, dst: Long, bytes: Long) = MemoryUtil.memCopy(src, dst, bytes) - fun byteBuffer(cap: Int): ByteBuffer { - return BufferUtils.createByteBuffer(cap) - } + /** + * Creates a new buffer of [cap] bytes + */ + fun byteBuffer(cap: Int) = BufferUtils.createByteBuffer(cap) +} + +val Int.kilobyte get() = this * 1000 +val Int.megabyte get() = this * 1000 * 1000 +val Int.gigabyte get() = this * 1000 * 1000 * 1000 + +val Int.kibibyte get() = this * 1024 +val Int.mebibyte get() = this * 1024 * 1024 +val Int.gibibyte get() = this * 1024 * 1024 * 1024 - val Buffer.capacity get() = capacity() +val Long.kilobyte get() = this * 1000 +val Long.megabyte get() = this * 1000 * 1000 +val Long.gigabyte get() = this * 1000 * 1000 * 1000 + +val Long.kibibyte get() = this * 1024 +val Long.mebibyte get() = this * 1024 * 1024 +val Long.gibibyte get() = this * 1024 * 1024 * 1024 + +/** + * Returns memory alignment for each CPU architecture + */ +fun alignment(): Int { + return when (System.getProperty("os.arch")?.lowercase()) { + "x86", "x86_64" -> 4 // 32-bit or 64-bit x86 + "arm", "armv7l", "aarch64" -> 4 // ARM architectures + else -> 8 // Default to 8 bytes alignment for other architectures + } } + +/** + * Returns how many bytes will be added to reach memory alignment + */ +fun padding(size: Int): Int = size % alignment() / 8 + +fun ByteBuffer.putTo(dst: ByteBuffer) { dst.put(this) } diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt b/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt deleted file mode 100644 index 9def4388c..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/gl/VaoUtils.kt +++ /dev/null @@ -1,60 +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.graphics.gl - -import net.minecraft.client.render.BufferRenderer -import org.lwjgl.opengl.GL30C.* -import java.nio.ByteBuffer - -object VaoUtils { - @JvmField - var lastIbo = 0 - private var prevIbo = 0 - - fun bindVertexArray(vao: Int) { - glBindVertexArray(vao) - BufferRenderer.currentVertexBuffer = null - } - - fun bindVertexBuffer(vbo: Int) = - glBindBuffer(GL_ARRAY_BUFFER, vbo) - - fun bindIndexBuffer(ibo: Int) { - if (ibo != 0) prevIbo = lastIbo - val targetIbo = if (ibo != 0) ibo else prevIbo - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, targetIbo) - } - - fun unbindVertexArray() = - bindVertexArray(0) - - fun unbindVertexBuffer() = - bindVertexBuffer(0) - - fun unbindIndexBuffer() = - bindIndexBuffer(0) - - fun enableVertexAttribute(i: Int) = - glEnableVertexAttribArray(i) - - fun vertexAttribute(index: Int, size: Int, type: Int, normalized: Boolean, stride: Int, pointer: Long) = - glVertexAttribPointer(index, size, type, normalized, stride, pointer) - - fun bufferData(target: Int, data: ByteBuffer, usage: Int) = - glBufferData(target, data, usage) -} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt index f247afa72..a223a2ba3 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt @@ -19,7 +19,6 @@ package com.lambda.graphics.renderer.esp.builders import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection -import com.lambda.graphics.renderer.esp.impl.ESPRenderer import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer import com.lambda.util.extension.max import com.lambda.util.extension.min diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt index 09e5b0b01..8f4ce984b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt @@ -21,10 +21,9 @@ import com.lambda.event.EventFlow.post import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener -import com.lambda.graphics.buffer.BufferUsage import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer -object StaticESP : StaticESPRenderer(BufferUsage.DYNAMIC, false) { +object StaticESP : StaticESPRenderer(false) { init { listener { clear() diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt index 4d55bb6b9..b3590251a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt @@ -17,6 +17,4 @@ package com.lambda.graphics.renderer.esp.impl -import com.lambda.graphics.buffer.BufferUsage - -open class DynamicESPRenderer : ESPRenderer(BufferUsage.DYNAMIC, true) +open class DynamicESPRenderer : ESPRenderer(true) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt index 815c196cb..f8eba5060 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/ESPRenderer.kt @@ -18,30 +18,26 @@ package com.lambda.graphics.renderer.esp.impl import com.lambda.Lambda.mc -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.BufferUsage -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.GlStateUtils.withFaceCulling import com.lambda.graphics.gl.GlStateUtils.withLineWidth import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.RenderSettings import com.lambda.util.extension.partialTicks -abstract class ESPRenderer( - usage: BufferUsage, - tickedMode: Boolean -) { +abstract class ESPRenderer(tickedMode: Boolean) { val shader: Shader - val faces: VAO - val outlines: VAO + val faces: VertexPipeline + val outlines: VertexPipeline init { val mode = if (tickedMode) dynamicMode else staticMode shader = mode.first - faces = VAO(VertexMode.TRIANGLES, mode.second, usage) - outlines = VAO(VertexMode.LINES, mode.second, usage) + faces = VertexPipeline(VertexMode.TRIANGLES, mode.second) + outlines = VertexPipeline(VertexMode.LINES, mode.second) } open fun upload() { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt index 6b0c7f67d..922883bbd 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt @@ -17,15 +17,11 @@ package com.lambda.graphics.renderer.esp.impl -import com.lambda.graphics.buffer.vao.IRenderContext -import com.lambda.graphics.buffer.BufferUsage +import com.lambda.graphics.buffer.IRenderContext import java.awt.Color import java.util.concurrent.ConcurrentHashMap -open class StaticESPRenderer( - usage: BufferUsage = BufferUsage.STATIC, - private val useVertexCaching: Boolean = true, -) : ESPRenderer(usage, false) { +open class StaticESPRenderer(private val useVertexCaching: Boolean = true) : ESPRenderer(false) { val faceVertices = ConcurrentHashMap() val outlineVertices = ConcurrentHashMap() diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt index 3ebe2e6d7..82186f8d1 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt @@ -18,9 +18,9 @@ package com.lambda.graphics.renderer.gui import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.shader.Shader import com.lambda.graphics.texture.Texture import com.lambda.module.modules.client.GuiSettings @@ -29,7 +29,7 @@ import com.lambda.util.math.Vec2d import org.lwjgl.glfw.GLFW.glfwGetTime object TextureRenderer { - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) private val shader = Shader("renderer/pos_tex") private val shaderColored = Shader("renderer/pos_tex_shady") @@ -56,7 +56,7 @@ object TextureRenderer { val pos1 = rect.leftTop val pos2 = rect.rightBottom - vao.use { + pipeline.use { grow(4) putQuad( @@ -67,8 +67,8 @@ object TextureRenderer { ) } - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt index 2aef5a310..68072de6c 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt @@ -17,9 +17,9 @@ package com.lambda.graphics.renderer.gui.font -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.renderer.gui.font.glyph.GlyphInfo import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.LambdaMoji @@ -33,7 +33,7 @@ class FontRenderer( private val font: LambdaFont, private val emojis: LambdaEmoji ) { - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) var scaleMultiplier = 1.0 @@ -46,7 +46,7 @@ class FontRenderer( color: Color = Color.WHITE, scale: Double = 1.0, shadow: Boolean = true, - ) = vao.use { + ) = pipeline.use { iterateText(text, scale, shadow, color) { char, pos1, pos2, color -> grow(4) putQuad( @@ -163,9 +163,9 @@ class FontRenderer( font.glyphs.bind() emojis.glyphs.bind() - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt index 30e79788f..cc484dc94 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt @@ -18,9 +18,9 @@ package com.lambda.graphics.renderer.gui.rect import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.GuiSettings import com.lambda.util.math.Vec2d @@ -30,7 +30,7 @@ abstract class AbstractRectRenderer( attribGroup: VertexAttrib.Group, val shader: Shader ) { - protected val vao = VAO(VertexMode.TRIANGLES, attribGroup) + protected val pipeline = VertexPipeline(VertexMode.TRIANGLES, attribGroup) fun render() { shader.use() @@ -40,8 +40,8 @@ abstract class AbstractRectRenderer( shader["u_Size"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) - vao.upload() - vao.render() - vao.clear() + pipeline.upload() + pipeline.render() + pipeline.clear() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt index ef570609d..f1883acdb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt @@ -17,7 +17,7 @@ package com.lambda.graphics.renderer.gui.rect -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.shader.Shader import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Rect @@ -42,7 +42,7 @@ class FilledRectRenderer : AbstractRectRenderer( rightBottom: Color = Color.WHITE, leftBottom: Color = Color.WHITE, shade: Boolean = false, - ) = vao.use { + ) = pipeline.use { val pos1 = rect.leftTop val pos2 = rect.rightBottom diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt index 202ee0e91..5f758af13 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt @@ -17,8 +17,8 @@ package com.lambda.graphics.renderer.gui.rect -import com.lambda.graphics.buffer.vao.IRenderContext -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.IRenderContext +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.shader.Shader import com.lambda.util.math.lerp import com.lambda.util.math.MathUtils.toInt @@ -53,7 +53,7 @@ class OutlineRectRenderer : AbstractRectRenderer( rightBottom: Color = Color.WHITE, leftBottom: Color = Color.WHITE, shade: Boolean = false, - ) = vao.use { + ) = pipeline.use { if (glowRadius < 1) return@use grow(verticesCount * 3) diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index 83341d827..52dc5b722 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -18,7 +18,7 @@ package com.lambda.graphics.texture import com.lambda.graphics.texture.TextureUtils.bindTexture -import org.lwjgl.opengl.GL13.glGenTextures +import org.lwjgl.opengl.GL45C.* open class Texture { val id = glGenTextures() diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt index 82e075c25..2fe308fbc 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt @@ -24,6 +24,7 @@ import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL45C.* import java.awt.* import java.awt.image.BufferedImage +import java.nio.ByteBuffer import kotlin.math.roundToInt import kotlin.math.sqrt @@ -32,7 +33,8 @@ object TextureUtils { private const val THREADED_COMPRESSION = false private val metricCache = mutableMapOf() - private val encoderPreset = PngEncoder() + + val encoderPreset = PngEncoder() .withCompressionLevel(COMPRESSION_LEVEL) .withMultiThreadedCompressionEnabled(THREADED_COMPRESSION) @@ -45,15 +47,6 @@ object TextureUtils { val width = bufferedImage.width val height = bufferedImage.height - // Here we cannot use GL_UNSIGNED_INT_8_8_8_8_REV or GL_UNSIGNED_INT_8_8_8_8 - // because the RGBA values are affected by the machine's endianness. - // On little-endian machines, you would read the data as - // 0xAABBGGRR and on big-endian machines as 0xRRGGBBAA. - // The solution is to use GL_UNSIGNED_BYTE and swap the bytes - // manually if necessary. (We won't need to) - // - // GL_UNSIGNED_BYTE -> [RR, GG, BB, AA] - // Array of floats normalized to [0.0, 1.0] -> [R, G, B, A] glTexImage2D(GL_TEXTURE_2D, lod, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, readImage(bufferedImage)) setupTexture(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR) @@ -78,7 +71,10 @@ object TextureUtils { glPixelStorei(GL_UNPACK_ALIGNMENT, 4) } - private fun readImage(bufferedImage: BufferedImage): Long { + fun readImage( + bufferedImage: BufferedImage, + format: NativeImage.Format = NativeImage.Format.RGBA, + ): Long { val bytes = encoderPreset .withBufferedImage(bufferedImage) .toBytes() @@ -88,9 +84,14 @@ object TextureUtils { .put(bytes) .flip() - return NativeImage.read(buffer).pointer + return readImage(buffer, format) } + fun readImage( + image: ByteBuffer, + format: NativeImage.Format = NativeImage.Format.RGBA, + ) = NativeImage.read(format, image).pointer + fun getCharImage(font: Font, codePoint: Char): BufferedImage? { if (!font.canDisplay(codePoint)) return null diff --git a/common/src/main/kotlin/com/lambda/module/Module.kt b/common/src/main/kotlin/com/lambda/module/Module.kt index 3b4ff6e3e..263531640 100644 --- a/common/src/main/kotlin/com/lambda/module/Module.kt +++ b/common/src/main/kotlin/com/lambda/module/Module.kt @@ -144,6 +144,14 @@ abstract class Module( onDisable { playSoundRandomly(LambdaSound.MODULE_OFF.event) } + + onEnable { + playSoundRandomly(LambdaSound.MODULE_ON.event) + } + + onDisable { + playSoundRandomly(LambdaSound.MODULE_OFF.event) + } } fun enable() { diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt index b128c4dfe..69e880f89 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt @@ -24,9 +24,9 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.buffer.VertexPipeline +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode import com.lambda.graphics.gl.GlStateUtils.withBlendFunc import com.lambda.graphics.gl.GlStateUtils.withDepth import com.lambda.graphics.gl.Matrices @@ -79,7 +79,7 @@ object Particles : Module( private val environmentSpeedV by setting("E Speed V", 0.1, 0.0..10.0, 0.1) { environment } private var particles = mutableListOf() - private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) + private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) private val shader = Shader("renderer/particle", "renderer/particle") init { @@ -96,9 +96,9 @@ object Particles : Module( shader.use() shader["u_CameraPosition"] = mc.gameRenderer.camera.pos - vao.upload() - withDepth(vao::render) - vao.clear() + pipeline.upload() + withDepth(pipeline::render) + pipeline.clear() } } @@ -198,7 +198,7 @@ object Particles : Module( val size = if (lay) environmentSize else sizeSetting * lerp(alpha, 0.5, 1.0) withVertexTransform(buildWorldProjection(position, size, projRotation)) { - vao.use { + pipeline.use { grow(4) // DO NOT FUCKING FORGOTEOIJTOWKET TO GROW (cost me an hour) putQuad( vec3m(-1.0, -1.0, 0.0).vec2(0.0, 0.0).color(color).end(), diff --git a/common/src/main/kotlin/com/lambda/util/extension/Other.kt b/common/src/main/kotlin/com/lambda/util/extension/Other.kt index 0d1f8079e..9dff4ea5c 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Other.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Other.kt @@ -17,6 +17,25 @@ package com.lambda.util.extension +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Executes the given block only if the object receiver is null + * Opposite of `Any?.let {}` + */ +@OptIn(ExperimentalContracts::class) +inline fun T?.ifNull(block: () -> Unit): T? { + contract { + callsInPlace(block, InvocationKind.AT_MOST_ONCE) + } + + if (this == null) block() + + return this +} + val Class<*>.isObject: Boolean get() = declaredFields.any { it.name == "INSTANCE" } diff --git a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt index 5ff6dcfbc..b397cf190 100644 --- a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt @@ -80,19 +80,7 @@ object MathUtils { return nextDouble(min, max) } - /** - * @return The smallest power of two that is greater than or equal to the input integer. - */ - fun Int.ceilToPOT(): Int { - var i = this - i-- - i = i or (i shr 1) - i = i or (i shr 2) - i = i or (i shr 4) - i = i or (i shr 8) - i = i or (i shr 16) - return ++i - } + fun Int.nextPowerOf2() = 2f.pow(ceil(log2(toFloat()))).toInt() inline val Int.sq: Int get() = this * this } diff --git a/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index 5bebe41df..97c389da3 100644 --- a/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -228,6 +228,7 @@ object WorldUtils { predicate: (FastVector, FluidState) -> Boolean = { _, _ -> true }, iterator: (FastVector, FluidState) -> Unit = { _, _ -> }, ) { + @Suppress("UNCHECKED_CAST") internalIteratePositions(pos, range, step) { position -> world.getFluidState(position.x, position.y, position.z).let { state -> val fulfilled = kClass.isInstance(state.fluid) && predicate(position, state)