diff --git a/gradle/scripts/moddevgradle.gradle b/gradle/scripts/moddevgradle.gradle index 8b78218109..71f8791613 100644 --- a/gradle/scripts/moddevgradle.gradle +++ b/gradle/scripts/moddevgradle.gradle @@ -42,6 +42,12 @@ neoForge { systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id } + clientEnforceLegacyBuffers { + client() + systemProperty "anvilcraft.enforceLegacyBuffers", "true" + jvmArgument("-XX:+CreateCoredumpOnCrash") + } + server { server() programArgument '--nogui' diff --git a/src/main/java/dev/dubhe/anvilcraft/api/power/SimplePowerGrid.java b/src/main/java/dev/dubhe/anvilcraft/api/power/SimplePowerGrid.java index dd2e09e6f5..615df21e26 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/power/SimplePowerGrid.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/power/SimplePowerGrid.java @@ -3,9 +3,9 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.dubhe.anvilcraft.AnvilCraft; -import dev.dubhe.anvilcraft.client.renderer.Line; import dev.dubhe.anvilcraft.client.support.PowerGridSupport; import dev.dubhe.anvilcraft.util.ColorUtil; +import dev.dubhe.anvilcraft.util.Line; import dev.dubhe.anvilcraft.util.ShapeUtil; import dev.dubhe.anvilcraft.util.VirtualThreadFactoryImpl; import lombok.Getter; diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CompileResult.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/CompileResult.java deleted file mode 100644 index 5ee32609e0..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CompileResult.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.dubhe.anvilcraft.api.rendering; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.VertexBuffer; -import com.mojang.blaze3d.vertex.VertexFormat; -import lombok.EqualsAndHashCode; -import net.minecraft.client.renderer.RenderType; -import org.lwjgl.opengl.GL15; -import org.lwjgl.opengl.GL15C; -import org.lwjgl.system.MemoryUtil; - -@EqualsAndHashCode -final class CompileResult { - private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false); - private final RenderType renderType; - private final int vertexCount; - private final int vertexSize; - private final long vertexBufferPtr; - final int indexCount; - private boolean freed = false; - - CompileResult( - RenderType renderType, - int vertexCount, - int vertexSize, - long vertexBufferPtr, - int indexCount - ) { - this.renderType = renderType; - this.vertexCount = vertexCount; - this.vertexSize = vertexSize; - this.vertexBufferPtr = vertexBufferPtr; - this.indexCount = indexCount; - } - - void upload(VertexBuffer vertexBuffer) { - if (freed) return; - VertexFormat.Mode mode = renderType.mode; - vertexBuffer.bind(); - if (vertexBuffer.format != null) { - vertexBuffer.format.clearBufferState(); - } - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBuffer.vertexBufferId); - renderType.format.setupBufferState(); - vertexBuffer.format = renderType.format; - GL15C.nglBufferData(GL15.GL_ARRAY_BUFFER, (long) vertexCount * vertexSize, vertexBufferPtr, GL15.GL_STATIC_DRAW); - RenderSystem.AutoStorageIndexBuffer indexBuffer = RenderSystem.getSequentialBuffer(mode); - if (indexBuffer != vertexBuffer.sequentialIndices || !indexBuffer.hasStorage(indexCount)) { - indexBuffer.bind(indexCount); - } - vertexBuffer.sequentialIndices = indexBuffer; - VertexBuffer.unbind(); - } - - void free() { - if (freed) return; - ALLOCATOR.free(vertexBufferPtr); - freed = true; - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/CompileResult.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/CompileResult.java new file mode 100644 index 0000000000..850fe6d86b --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/CompileResult.java @@ -0,0 +1,48 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation; + +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.GlVertexBuffer; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.QuadSortingState; +import lombok.EqualsAndHashCode; +import net.minecraft.client.renderer.RenderType; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; +import org.lwjgl.system.MemoryUtil; + +@EqualsAndHashCode +public final class CompileResult implements Disposable { + private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false); + private final RenderType renderType; + private final int vertexCount; + private final int vertexSize; + private final long vertexBufferPtr; + private final int indexCount; + @Nullable + private final QuadSortingState sortingState; + private boolean freed = false; + + public CompileResult( + RenderType renderType, + int vertexCount, + int vertexSize, + long vertexBufferPtr, + int indexCount, + @Nullable QuadSortingState sortingState + ) { + this.renderType = renderType; + this.vertexCount = vertexCount; + this.vertexSize = vertexSize; + this.vertexBufferPtr = vertexBufferPtr; + this.indexCount = indexCount; + this.sortingState = sortingState; + } + + public void upload(GlVertexBuffer vertexBuffer) { + vertexBuffer.upload(vertexBufferPtr, vertexCount * vertexSize, vertexCount, indexCount, sortingState, new Vector3f(), this); + } + + public void dispose() { + if (freed) return; + ALLOCATOR.free(vertexBufferPtr); + freed = true; + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/Disposable.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/Disposable.java new file mode 100644 index 0000000000..fbc9359e53 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/Disposable.java @@ -0,0 +1,8 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation; + +public interface Disposable { + /** + * It is guaranteed to run on Render Thread. + */ + void dispose(); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/FullyBufferedBufferSource.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/FullyBufferedBufferSource.java similarity index 69% rename from src/main/java/dev/dubhe/anvilcraft/api/rendering/FullyBufferedBufferSource.java rename to src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/FullyBufferedBufferSource.java index 2d1241858d..4a8b207ab7 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/rendering/FullyBufferedBufferSource.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/FullyBufferedBufferSource.java @@ -1,30 +1,33 @@ -package dev.dubhe.anvilcraft.api.rendering; +package dev.dubhe.anvilcraft.api.rendering.foundation; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.ByteBufferBuilder; import com.mojang.blaze3d.vertex.MeshData; -import com.mojang.blaze3d.vertex.VertexBuffer; import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.GlVertexBuffer; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.QuadSortingState; import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMaps; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import lombok.Getter; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import org.lwjgl.system.MemoryUtil; import javax.annotation.ParametersAreNonnullByDefault; -import java.util.HashMap; import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.concurrent.ConcurrentHashMap; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class FullyBufferedBufferSource extends MultiBufferSource.BufferSource implements AutoCloseable { +public class FullyBufferedBufferSource extends MultiBufferSource.BufferSource implements Disposable { private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false); - private final Map byteBuffers = new HashMap<>(); - private final Map bufferBuilders = new HashMap<>(); - final Reference2IntMap indexCountMap = new Reference2IntOpenHashMap<>(); + private final Map byteBuffers = new ConcurrentHashMap<>(); + private final Map bufferBuilders = new ConcurrentHashMap<>(); + @Getter + private final Reference2IntMap indexCountMap = Reference2IntMaps.synchronize(new Reference2IntOpenHashMap<>()); public FullyBufferedBufferSource() { super(null, null); @@ -50,12 +53,9 @@ public boolean isEmpty() { public void endBatch(RenderType renderType) { } - public void upload( - Function vertexBufferGetter, - Consumer runner - ) { + public void upload(CompileContext context) { for (RenderType renderType : bufferBuilders.keySet()) { - runner.accept(() -> { + context.submitUploadTask(() -> { BufferBuilder bufferBuilder = bufferBuilders.get(renderType); ByteBufferBuilder byteBuffer = byteBuffers.get(renderType); long ptr = byteBuffer.pointer; @@ -64,19 +64,23 @@ public void upload( long allocated = ALLOCATOR.malloc(compiledVertices); MemoryUtil.memCopy(ptr, allocated, compiledVertices); MeshData mesh = bufferBuilder.build(); - if (mesh != null) { - mesh.close(); + if (mesh == null) return; + QuadSortingState state = null; + + if (renderType.sortOnUpload) { + state = QuadSortingState.fromMesh(mesh); } + mesh.close(); CompileResult compileResult = new CompileResult( renderType, bufferBuilder.vertices, renderType.format.getVertexSize(), allocated, - renderType.mode.indexCount(bufferBuilder.vertices) + renderType.mode.indexCount(bufferBuilder.vertices), + state ); indexCountMap.put(renderType, renderType.mode.indexCount(bufferBuilder.vertices)); - compileResult.upload(vertexBufferGetter.apply(renderType)); - compileResult.free(); + compileResult.upload(context.getOrCreateBuffer(renderType, mesh.drawState().indexType())); } byteBuffer.close(); bufferBuilders.remove(renderType); @@ -90,7 +94,13 @@ public void close(RenderType renderType) { builder.close(); } - public void close() { + public void dispose() { byteBuffers.keySet().forEach(this::close); } + + public interface CompileContext { + GlVertexBuffer getOrCreateBuffer(RenderType renderType, VertexFormat.IndexType indexType); + + void submitUploadTask(Runnable runnable); + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/QuadSorter.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/QuadSorter.java new file mode 100644 index 0000000000..2c8fc89758 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/QuadSorter.java @@ -0,0 +1,19 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation; + +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.QuadSortingState; +import it.unimi.dsi.fastutil.ints.IntArrays; +import org.joml.Vector3f; + +public class QuadSorter { + public static int[] buildSortedIndexByDistance(QuadSortingState state, Vector3f point){ + float[] distances = new float[state.quadCenters().length]; + int[] indexes = new int[state.quadCenters().length]; + + for (int i = 0; i < state.quadCenters().length; i++) { + distances[i] = state.quadCenters()[i].distanceSquared(point); + indexes[i] = i; + } + IntArrays.mergeSort(indexes, (a,b) -> Float.compare(distances[a], distances[b])); + return indexes; + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/BufferHost.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/BufferHost.java new file mode 100644 index 0000000000..7b67bd026b --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/BufferHost.java @@ -0,0 +1,4 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer; + +public interface BufferHost { +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorage.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorage.java new file mode 100644 index 0000000000..e5a76bb446 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorage.java @@ -0,0 +1,53 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer; + +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; +import org.lwjgl.opengl.GL; + +import static org.lwjgl.opengl.GL45.*; + +public abstract class GlBufferStorage implements Disposable { + public static final boolean BUFFER_STORAGE_SUPPORT = GL.getCapabilities().GL_ARB_buffer_storage && System.getProperty("anvilcraft.enforceLegacyBuffers") == null; + protected final int glBufferId; + protected final int target; + protected boolean valid = true; + + static { + if (BUFFER_STORAGE_SUPPORT) { + System.out.println("Using GL_ARB_buffer_storage as buffer storage."); + } + } + + GlBufferStorage(int target, C configureContext) { + this.target = target; + this.glBufferId = glGenBuffers(); + } + + public abstract void setupBufferState(C configureContext); + + public void upload(long ptr, long size, Disposable uploadSrc) { + this.upload(ptr, size); + uploadSrc.dispose(); + } + + /** + * Runs on worker thread + */ + public abstract void upload(long ptr, long size); + + public void bind() { + glBindBuffer(target, glBufferId); + } + + @Override + public void dispose() { + if (!valid) return; + bind(); + glDeleteBuffers(glBufferId); + unbind(); + valid = false; + } + + public void unbind() { + glBindBuffer(target, 0); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorageLegacy.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorageLegacy.java new file mode 100644 index 0000000000..30d4cb7115 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorageLegacy.java @@ -0,0 +1,40 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; + +import static org.lwjgl.opengl.GL45.*; + +public abstract class GlBufferStorageLegacy extends GlBufferStorage { + + protected GlBufferStorageLegacy(int target,C configureContext) { + super(target, configureContext); + bind(); + this.setupBufferState(configureContext); + } + + @Override + public void upload(long ptr, long size, Disposable disposable) { + if (RenderSystem.isOnRenderThread()) { + nglBufferData(this.target, size, ptr, GL_STATIC_DRAW); + disposable.dispose(); + return; + } + RenderSystem.recordRenderCall(() -> { + nglBufferData(this.target, size, ptr, GL_STATIC_DRAW); + disposable.dispose(); + } + ); + } + + public void upload(long ptr, long size) { + if (RenderSystem.isOnRenderThread()) { + nglBufferData(this.target, size, ptr, GL_STATIC_DRAW); + return; + } + RenderSystem.recordRenderCall(() -> { + nglBufferData(this.target, size, ptr, GL_STATIC_DRAW); + } + ); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorageModern.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorageModern.java new file mode 100644 index 0000000000..bdef0d0bcf --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/GlBufferStorageModern.java @@ -0,0 +1,43 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer; + +import net.minecraft.client.renderer.RenderType; +import org.lwjgl.opengl.ARBBufferStorage; +import org.lwjgl.system.MemoryUtil; + +import static org.lwjgl.opengl.GL45.*; + +public abstract class GlBufferStorageModern extends GlBufferStorage { + public static final long BUFFER_SIZE = RenderType.SMALL_BUFFER_SIZE; + public static final int FLAGS = ARBBufferStorage.GL_MAP_PERSISTENT_BIT + | ARBBufferStorage.GL_MAP_COHERENT_BIT + | GL_MAP_WRITE_BIT + | GL_DYNAMIC_STORAGE_BIT; + + private long clientPtr; + + protected GlBufferStorageModern(int target,C configureContext) { + super(target, configureContext); + bind(); + this.setupBufferState(configureContext); + ARBBufferStorage.glBufferStorage( + target, + BUFFER_SIZE, + FLAGS + ); + clientPtr = nglMapBufferRange(target, 0, BUFFER_SIZE, GL_MAP_WRITE_BIT); + } + + @Override + public void dispose() { + if (!valid)return; + bind(); + glUnmapBuffer(target); + clientPtr = 0; + super.dispose(); + } + + @Override + public void upload(long ptr, long size) { + MemoryUtil.memCopy(ptr, clientPtr, size); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/ring/RingBuffer.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/ring/RingBuffer.java new file mode 100644 index 0000000000..4f9d905fd0 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/ring/RingBuffer.java @@ -0,0 +1,65 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.ring; + +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.BufferHost; + +public class RingBuffer, C extends BufferHost> implements Disposable { + public static final int DEFAULT_SIZE = 5; + + private final int size; + private int currentUsing = 0; + private final C context; + private final RingBufferElement.Factory factory; + private final RingBufferElement[] elements; + + public RingBuffer(int size, C context, RingBufferElement.Factory factory) { + this.size = size; + this.context = context; + this.factory = factory; + elements = new RingBufferElement[size]; + } + + public RingBuffer(C context, RingBufferElement.Factory factory) { + this(DEFAULT_SIZE, context, factory); + } + + @SuppressWarnings("unchecked") + public T get() { + if (currentUsing < size - 1) { + T element = (T) elements[currentUsing++]; + if (element == null) { + elements[currentUsing] = element = factory.create(context); + } + element.waitForReady(); + element.setup(); + return element; + } + currentUsing = 0; + T element = (T) elements[0]; + if (element == null) { + elements[currentUsing] = element = factory.create(context); + } + element.waitForReady(); + element.setup(); + return element; + } + + @SuppressWarnings("unchecked") + public T current() { + T element = (T) elements[currentUsing]; + if (element == null) { + elements[currentUsing] = element = factory.create(context); + } + return element; + } + + + @Override + public void dispose() { + for (RingBufferElement element : elements) { + if (element != null) { + element.dispose(); + } + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/ring/RingBufferElement.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/ring/RingBufferElement.java new file mode 100644 index 0000000000..8aff8ad028 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/ring/RingBufferElement.java @@ -0,0 +1,15 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.ring; + +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.BufferHost; + +public interface RingBufferElement extends Disposable { + + void waitForReady(); + + void setup(); + + public interface Factory, C extends BufferHost> { + T create(C context); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/GlVertexBuffer.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/GlVertexBuffer.java new file mode 100644 index 0000000000..9052daca09 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/GlVertexBuffer.java @@ -0,0 +1,93 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; +import dev.dubhe.anvilcraft.api.rendering.foundation.QuadSorter; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorage; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.index.GlIndexBuffer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.world.phys.Vec3; +import net.neoforged.fml.ModList; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL46; + +import static org.lwjgl.opengl.GL45.*; + +public class GlVertexBuffer implements Disposable { + private final int arrayObjectId; + private final GlBufferStorage vertexBuffer; + private final GlIndexBuffer indexBuffer; + private final RenderType renderType; + private final VertexFormat.IndexType indexType; + private QuadSortingState sortingState; + + public GlVertexBuffer(RenderType renderType) { + this(renderType, VertexFormat.IndexType.SHORT); + } + + public GlVertexBuffer(RenderType renderType, VertexFormat.IndexType indexType) { + if (renderType.mode != VertexFormat.Mode.QUADS) throw new UnsupportedOperationException(); + this.indexType = indexType; + this.renderType = renderType; + this.arrayObjectId = glGenVertexArrays(); + glBindVertexArray(arrayObjectId); + this.vertexBuffer = GlVertexBufferStorage.create(renderType.format); + this.indexBuffer = GlIndexBuffer.forQuad(indexType); + glBindVertexArray(0); + } + + public void upload(long ptr, int size, int vertexCount, int indexCount, Disposable uploadSrc) { + this.upload(ptr, size, vertexCount, indexCount, null, null, uploadSrc); + } + + public void upload(long ptr, int size, int vertexCount, int indexCount, @Nullable QuadSortingState sortingState, @Nullable Vector3f origin, Disposable uploadSrc) { + this.sortingState = sortingState; +// if (sortingState == null) { +// indexBuffer.fillContents(vertexCount); +// } else { +// resortVertices(origin); +// } + indexBuffer.fillContents(vertexCount, indexCount); + this.vertexBuffer.upload(ptr, size, uploadSrc); + } + + public void resortVertices(Vector3f origin) { + int[] indexes = QuadSorter.buildSortedIndexByDistance(sortingState, origin); + indexBuffer.fromSorted(indexes); + } + + public void bind() { + glBindVertexArray(arrayObjectId); + } + + public void unbind() { + glBindVertexArray(0); + } + + @Override + public void dispose() { + vertexBuffer.dispose(); + indexBuffer.dispose(); + glDeleteVertexArrays(arrayObjectId); + } + + public int getIndexType() { + return indexType.asGLType; + } + + public void bindIndexBuffer() { + indexBuffer.bind(); + } + + public void drawElements(Vec3 cameraPosition) { + if (renderType.sortOnUpload) { + //this.resortVertices(cameraPosition.toVector3f()); + } + GL46.glBindVertexArray(arrayObjectId); + GL46.glDrawElements(GL46.GL_TRIANGLES, indexBuffer.getIndexCount(), indexType.asGLType, 0L); + GL46.glBindVertexArray(0); + RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).bind(6); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/GlVertexBufferStorage.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/GlVertexBufferStorage.java new file mode 100644 index 0000000000..cbe349f47e --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/GlVertexBufferStorage.java @@ -0,0 +1,48 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex; + +import com.mojang.blaze3d.platform.GlConst; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorage; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorageLegacy; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorageModern; +import lombok.Getter; + +public final class GlVertexBufferStorage { + + public static GlBufferStorage create(VertexFormat format) { + if (GlBufferStorage.BUFFER_STORAGE_SUPPORT) { + return new Modern(format); + } + return new Legacy(format); + } + + public static class Legacy extends GlBufferStorageLegacy { + @Getter + private final VertexFormat format; + + public Legacy(VertexFormat format) { + super(GlConst.GL_ARRAY_BUFFER, format); + this.format = format; + } + + @Override + public void setupBufferState(VertexFormat vertexFormat) { + vertexFormat.setupBufferState(); + } + } + + public static class Modern extends GlBufferStorageModern { + @Getter + private final VertexFormat format; + + public Modern(VertexFormat format) { + super(GlConst.GL_ARRAY_BUFFER, format); + this.format = format; + } + + @Override + public void setupBufferState(VertexFormat vertexFormat) { + vertexFormat.setupBufferState(); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/QuadSortingState.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/QuadSortingState.java new file mode 100644 index 0000000000..ea10530f8c --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/QuadSortingState.java @@ -0,0 +1,41 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex; + +import com.mojang.blaze3d.vertex.MeshData; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.blaze3d.vertex.VertexFormatElement; +import org.joml.Vector3f; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public record QuadSortingState(Vector3f[] quadCenters) { + public static QuadSortingState fromMesh(MeshData meshData) { + ByteBuffer byteBuffer = meshData.vertexBuffer(); + int vertexCount = meshData.drawState().vertexCount(); + VertexFormat format = meshData.drawState().format(); + int offset = format.getOffset(VertexFormatElement.POSITION); + if (offset == -1) { + throw new IllegalArgumentException("Cannot identify quad centers with no position element"); + } else { + FloatBuffer floatbuffer = byteBuffer.asFloatBuffer(); + int j = format.getVertexSize() / 4; + int vertexSize = j * 4; + int quadCount = vertexCount / 4; + Vector3f[] avector3f = new Vector3f[quadCount]; + + for (int i = 0; i < quadCount; i++) { + int j1 = i * vertexSize + offset; + int k1 = j1 + j * 2; + float f = floatbuffer.get(j1 + 0); + float f1 = floatbuffer.get(j1 + 1); + float f2 = floatbuffer.get(j1 + 2); + float f3 = floatbuffer.get(k1 + 0); + float f4 = floatbuffer.get(k1 + 1); + float f5 = floatbuffer.get(k1 + 2); + avector3f[i] = new Vector3f((f + f3) / 2.0F, (f1 + f4) / 2.0F, (f2 + f5) / 2.0F); + } + + return new QuadSortingState(avector3f); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlIndexBuffer.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlIndexBuffer.java new file mode 100644 index 0000000000..337afe2a2e --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlIndexBuffer.java @@ -0,0 +1,26 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.index; + +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; + +public interface GlIndexBuffer extends Disposable { + /** + * Called on worker threads + */ + void fillContents(int vertexCount, int indexCount); + + /** + * Called on worker threads= + */ + void fromSorted(int[] sortedIndex); + + void bind(); + + void unbind(); + + int getIndexCount(); + + static GlIndexBuffer forQuad(VertexFormat.IndexType indexType){ + return new GlQuadIndexBuffer(indexType); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlIndexBufferStorage.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlIndexBufferStorage.java new file mode 100644 index 0000000000..8ce0b6624d --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlIndexBufferStorage.java @@ -0,0 +1,36 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.index; + +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorage; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorageLegacy; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorageModern; +import org.lwjgl.opengl.GL45; + +public class GlIndexBufferStorage { + public static GlBufferStorage create() { + if (GlBufferStorage.BUFFER_STORAGE_SUPPORT) { + return new Modern(); + } + return new Legacy(); + } + + public static class Legacy extends GlBufferStorageLegacy { + + public Legacy() { + super(GL45.GL_ELEMENT_ARRAY_BUFFER, null); + } + + @Override + public void setupBufferState(Void v) { + } + } + + public static class Modern extends GlBufferStorageModern { + public Modern() { + super(GL45.GL_ELEMENT_ARRAY_BUFFER, null); + } + + @Override + public void setupBufferState(Void v) { + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlQuadIndexBuffer.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlQuadIndexBuffer.java new file mode 100644 index 0000000000..86e1a3b7a6 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/foundation/buffer/vertex/index/GlQuadIndexBuffer.java @@ -0,0 +1,91 @@ +package dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.index; + +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.GlBufferStorage; +import lombok.Getter; +import org.lwjgl.system.MemoryUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GlQuadIndexBuffer implements GlIndexBuffer { + private final Logger logger = LoggerFactory.getLogger("GlQuadIndexBuffer"); + private final MemoryUtil.MemoryAllocator alloc = MemoryUtil.getAllocator(); + private long ptr = alloc.calloc(1024, 4); + private final VertexFormat.IndexType indexType; + private final GlBufferStorage backedBuffer; + @Getter + private int indexCount; + + protected GlQuadIndexBuffer(VertexFormat.IndexType indexType) { + this.indexType = indexType; + this.backedBuffer = GlIndexBufferStorage.create(); + } + + private void write(int index, int value) { + logger.info("{}: {} -> {}", this, index, value); + if (indexType == VertexFormat.IndexType.SHORT) { + MemoryUtil.memPutShort(ptr + index * 2L, (short) value); + return; + } + if (indexType == VertexFormat.IndexType.INT) { + MemoryUtil.memPutInt(ptr + index * 4L, value); + } + } + + private boolean hasEnoughStorage(int required) { + return indexCount >= required; + } + + private void ensureStorage(int required) { + alloc.free(ptr); + this.ptr = alloc.calloc(required, 4); + this.indexCount = required; + } + + + @Override + public void dispose() { + if (ptr != 0) { + alloc.free(ptr); + ptr = 0; + } + backedBuffer.dispose(); + } + + @SuppressWarnings("PointlessArithmeticExpression") + @Override + public void fillContents(int vertexCount, int indexCount) { + ensureStorage(indexCount); + int quadCount = vertexCount / 4; + for (int i = 0, j = 0, k = 0; i < quadCount; i++, k += 4) { + write(j++, k + 0); + write(j++, k + 1); + write(j++, k + 2); + write(j++, k + 2); + write(j++, k + 3); + write(j++, k + 0); + } + backedBuffer.bind(); + backedBuffer.upload(ptr, (long) indexCount * indexType.bytes); + } + + @Override + public void fromSorted(int[] sortedIndex) { + ensureStorage(sortedIndex.length); + for (int i = 0; i < sortedIndex.length; i++) { + write(i, sortedIndex[i]); + } + backedBuffer.bind(); + backedBuffer.upload(ptr, (long) indexCount * indexType.bytes); + } + + @Override + public void bind() { + backedBuffer.bind(); + } + + @Override + public void unbind() { + backedBuffer.unbind(); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBERenderingPipeline.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBERenderingPipeline.java similarity index 95% rename from src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBERenderingPipeline.java rename to src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBERenderingPipeline.java index 9c93e3c165..6201733d6a 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBERenderingPipeline.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBERenderingPipeline.java @@ -1,4 +1,4 @@ -package dev.dubhe.anvilcraft.api.rendering; +package dev.dubhe.anvilcraft.api.rendering.pipeline.cached; import lombok.Getter; import net.minecraft.client.multiplayer.ClientLevel; @@ -68,7 +68,7 @@ public void submitCompileTask(Runnable task) { } public void releaseBuffers() { - renderRegions.values().forEach(RenderRegion::releaseBuffers); + renderRegions.values().forEach(RenderRegion::dispose); valid = false; } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBlockEntityRenderer.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBlockEntityRenderer.java similarity index 85% rename from src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBlockEntityRenderer.java rename to src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBlockEntityRenderer.java index 0bc1c0e225..56b63b9811 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBlockEntityRenderer.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBlockEntityRenderer.java @@ -1,4 +1,4 @@ -package dev.dubhe.anvilcraft.api.rendering; +package dev.dubhe.anvilcraft.api.rendering.pipeline.cached; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.renderer.MultiBufferSource; diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBlockEntityRenderers.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBlockEntityRenderers.java similarity index 94% rename from src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBlockEntityRenderers.java rename to src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBlockEntityRenderers.java index c0fc129ea4..c1544b8e92 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/rendering/CacheableBlockEntityRenderers.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/CacheableBlockEntityRenderers.java @@ -1,4 +1,4 @@ -package dev.dubhe.anvilcraft.api.rendering; +package dev.dubhe.anvilcraft.api.rendering.pipeline.cached; import dev.dubhe.anvilcraft.client.renderer.laser.LaserRenderer; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/RegionBuffers.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/RegionBuffers.java new file mode 100644 index 0000000000..dddab8d1b8 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/RegionBuffers.java @@ -0,0 +1,79 @@ +package dev.dubhe.anvilcraft.api.rendering.pipeline.cached; + +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; +import dev.dubhe.anvilcraft.api.rendering.foundation.FullyBufferedBufferSource; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.ring.RingBufferElement; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.GlVertexBuffer; +import dev.dubhe.anvilcraft.api.rendering.util.SyncSupport; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.client.renderer.RenderType; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class RegionBuffers implements RingBufferElement, FullyBufferedBufferSource.CompileContext { + private final SyncSupport syncSupport = new SyncSupport(); + private final Map vertexBuffers = new ConcurrentHashMap<>(); + private final RenderRegion renderRegion; + + public RegionBuffers(RenderRegion renderRegion) { + this.renderRegion = renderRegion; + } + + @Override + public void waitForReady() { + if (!syncSupport.isSyncSet()){ + return; + } + if (!syncSupport.isSyncSignaled()){ + syncSupport.waitSync(); + } + syncSupport.deleteSync(); + syncSupport.reset(); + } + + @Override + public void setup() { + syncSupport.setSync(); + } + + @Override + public void dispose() { + for (GlVertexBuffer value : vertexBuffers.values()) { + value.dispose(); + } + } + + @Override + public GlVertexBuffer getOrCreateBuffer(RenderType renderType, VertexFormat.IndexType indexType) { + GlVertexBuffer buffer = vertexBuffers.get(renderType); + if (buffer != null) { + return buffer; + } + buffer = new GlVertexBuffer(renderType, indexType); + vertexBuffers.put(renderType, buffer); + return buffer; + } + + @Override + public void submitUploadTask(Runnable runnable) { + renderRegion.getPipeline().submitUploadTask(runnable); + } + + @Nullable + public GlVertexBuffer getBuffer(RenderType renderType) { + return vertexBuffers.get(renderType); + } + + public Collection allRenderPasses() { + return vertexBuffers.keySet(); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/RenderRegion.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/RenderRegion.java similarity index 77% rename from src/main/java/dev/dubhe/anvilcraft/api/rendering/RenderRegion.java rename to src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/RenderRegion.java index 54cc20779a..a780b005a8 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/rendering/RenderRegion.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/pipeline/cached/RenderRegion.java @@ -1,15 +1,20 @@ -package dev.dubhe.anvilcraft.api.rendering; +package dev.dubhe.anvilcraft.api.rendering.pipeline.cached; import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.shaders.Uniform; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexBuffer; import com.mojang.blaze3d.vertex.VertexFormat; +import dev.dubhe.anvilcraft.api.rendering.foundation.Disposable; +import dev.dubhe.anvilcraft.api.rendering.foundation.FullyBufferedBufferSource; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.BufferHost; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.ring.RingBuffer; +import dev.dubhe.anvilcraft.api.rendering.foundation.buffer.vertex.GlVertexBuffer; import dev.dubhe.anvilcraft.client.init.ModRenderTypes; import dev.dubhe.anvilcraft.client.renderer.RenderState; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import lombok.Getter; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.ShaderInstance; @@ -22,20 +27,19 @@ import org.lwjgl.opengl.GL15; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -public class RenderRegion { +public class RenderRegion implements BufferHost, Disposable { public static final List BLOOM_RENDERTYPES = List.of( ModRenderTypes.LASER ); private final ChunkPos chunkPos; - private final Map buffers = new HashMap<>(); + private final RingBuffer buffers = new RingBuffer<>(this, RegionBuffers::new); private Reference2IntMap indexCountMap = new Reference2IntOpenHashMap<>(); private final Set blockEntityList = new HashSet<>(); + @Getter private final CacheableBERenderingPipeline pipeline; private final Minecraft minecraft = Minecraft.getInstance(); private RebuildTask lastRebuildTask; @@ -54,11 +58,11 @@ public void update(BlockEntity be) { blockEntityList.removeIf(BlockEntity::isRemoved); if (be.isRemoved()) { blockEntityList.remove(be); - pipeline.submitCompileTask(new RebuildTask()); + pipeline.submitCompileTask(new RebuildTask(buffers.get())); return; } blockEntityList.add(be); - pipeline.submitCompileTask(new RebuildTask()); + pipeline.submitCompileTask(new RebuildTask(buffers.get())); } public void blockRemoved(BlockEntity be) { @@ -67,7 +71,7 @@ public void blockRemoved(BlockEntity be) { } blockEntityList.remove(be); blockEntityList.removeIf(BlockEntity::isRemoved); - pipeline.submitCompileTask(new RebuildTask()); + pipeline.submitCompileTask(new RebuildTask(buffers.get())); } public void renderBloomed(Matrix4f frustumMatrix, Matrix4f projectionMatrix) { @@ -75,16 +79,7 @@ public void renderBloomed(Matrix4f frustumMatrix, Matrix4f projectionMatrix) { } public void render(Matrix4f frustumMatrix, Matrix4f projectionMatrix) { - renderInternal(frustumMatrix, projectionMatrix, buffers.keySet(), RenderState::levelStage); - } - - public VertexBuffer getBuffer(RenderType renderType) { - if (buffers.containsKey(renderType)) { - return buffers.get(renderType); - } - VertexBuffer vb = new VertexBuffer(VertexBuffer.Usage.STATIC); - buffers.put(renderType, vb); - return vb; + renderInternal(frustumMatrix, projectionMatrix, buffers.current().allRenderPasses(), RenderState::levelStage); } private void renderInternal( @@ -102,20 +97,21 @@ private void renderInternal( return; } for (RenderType renderType : renderTypes) { - VertexBuffer vb = buffers.get(renderType); + GlVertexBuffer vb = this.getPresentBuffer(renderType); if (vb == null) continue; stateSwitcher.run(); renderLayer(renderType, vb, frustumMatrix, projectionMatrix, cameraPosition, window); } } - public void releaseBuffers() { - buffers.values().forEach(VertexBuffer::close); + protected GlVertexBuffer getPresentBuffer(RenderType renderType) { + RegionBuffers current = buffers.current(); + return current.getBuffer(renderType); } private void renderLayer( RenderType renderType, - VertexBuffer vertexBuffer, + GlVertexBuffer vertexBuffer, Matrix4f frustumMatrix, Matrix4f projectionMatrix, Vec3 cameraPosition, @@ -136,17 +132,25 @@ private void renderLayer( ); uniform.upload(); } - vertexBuffer.bind(); - GL11.glDrawElements(GL15.GL_TRIANGLES, indexCount, vertexBuffer.sequentialIndices.type().asGLType, 0L); - VertexBuffer.unbind(); + vertexBuffer.drawElements(cameraPosition); if (uniform != null) { uniform.set(0.0F, 0.0F, 0.0F); } renderType.clearRenderState(); } + @Override + public void dispose() { + buffers.dispose(); + } + private class RebuildTask implements Runnable { private boolean cancelled = false; + private final RegionBuffers buffers; + + private RebuildTask(RegionBuffers buffers) { + this.buffers = buffers; + } @Override public void run() { @@ -156,7 +160,7 @@ public void run() { FullyBufferedBufferSource bufferSource = new FullyBufferedBufferSource(); for (BlockEntity be : blockEntityList) { if (cancelled) { - bufferSource.close(); + bufferSource.dispose(); return; } CacheableBlockEntityRenderer renderer = CacheableBlockEntityRenderers.get(be.getType()); @@ -176,11 +180,8 @@ public void run() { poseStack.popPose(); } RenderRegion.this.isEmpty = bufferSource.isEmpty(); - bufferSource.upload( - RenderRegion.this::getBuffer, - pipeline::submitUploadTask - ); - RenderRegion.this.indexCountMap = bufferSource.indexCountMap; + bufferSource.upload(buffers); + RenderRegion.this.indexCountMap = bufferSource.getIndexCountMap(); lastRebuildTask = null; } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/SyncSupport.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/SyncSupport.java new file mode 100644 index 0000000000..d77e9fd5a7 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/SyncSupport.java @@ -0,0 +1,32 @@ +package dev.dubhe.anvilcraft.api.rendering.util; + +import static org.lwjgl.opengl.GL45.*; + +public class SyncSupport { + private long syncObject = -1; + + public boolean isSyncSet() { + return syncObject != -1; + } + + public boolean isSyncSignaled() { + return glGetSynci(syncObject, GL_SYNC_STATUS, null) == GL_SIGNALED; + } + + public void waitSync() { + glClientWaitSync(syncObject, GL_SYNC_FLUSH_COMMANDS_BIT, Long.MAX_VALUE); + } + + public void setSync() { + syncObject = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + public void deleteSync() { + glDeleteSync(syncObject); + } + + public void reset() { + this.syncObject = -1; + } + +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/Kernel32.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/Kernel32.java new file mode 100644 index 0000000000..2192529337 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/Kernel32.java @@ -0,0 +1,103 @@ +// from embeddium project +package dev.dubhe.anvilcraft.api.rendering.util.thirdparty; + +import java.nio.ByteBuffer; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.APIUtil; +import org.lwjgl.system.JNI; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.system.SharedLibrary; + +public class Kernel32 { + private static final SharedLibrary LIBRARY = APIUtil.apiCreateLibrary("kernel32"); + private static final int MAX_PATH = 32767; + private static final int GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 1; + private static final int GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4; + private static final long PFN_GetCommandLineW; + private static final long PFN_SetEnvironmentVariableW; + private static final long PFN_GetModuleHandleExW; + private static final long PFN_GetLastError; + private static final long PFN_GetModuleFileNameW; + + public static void setEnvironmentVariable(String name, @Nullable String value) { + try (MemoryStack stack = MemoryStack.stackPush()) { + ByteBuffer lpNameBuf = stack.malloc(16, MemoryUtil.memLengthUTF16(name, true)); + MemoryUtil.memUTF16(name, true, lpNameBuf); + ByteBuffer lpValueBuf = null; + if (value != null) { + lpValueBuf = stack.malloc(16, MemoryUtil.memLengthUTF16(value, true)); + MemoryUtil.memUTF16(value, true, lpValueBuf); + } + + JNI.callPPI(MemoryUtil.memAddress0(lpNameBuf), MemoryUtil.memAddressSafe(lpValueBuf), PFN_SetEnvironmentVariableW); + } + + } + + public static long getCommandLine() { + return JNI.callP(PFN_GetCommandLineW); + } + + public static long getModuleHandleByNames(String[] names) { + for(String name : names) { + long handle = getModuleHandleByName(name); + if (handle != 0L) { + return handle; + } + } + + throw new RuntimeException("Could not obtain handle of module"); + } + + public static long getModuleHandleByName(String name) { + try (MemoryStack stack = MemoryStack.stackPush()) { + ByteBuffer lpFunctionNameBuf = stack.malloc(16, MemoryUtil.memLengthUTF16(name, true)); + MemoryUtil.memUTF16(name, true, lpFunctionNameBuf); + PointerBuffer phModule = stack.callocPointer(1); + int result = JNI.callPPI(1, MemoryUtil.memAddress(lpFunctionNameBuf), MemoryUtil.memAddress(phModule), PFN_GetModuleHandleExW); + if (result == 0) { + int error = getLastError(); + switch (error) { + case 126 -> { + return 0L; + } + default -> throw new RuntimeException("GetModuleHandleEx failed, error=" + error); + } + } else { + return phModule.get(0); + } + } + } + + public static String getModuleFileName(long phModule) { + ByteBuffer lpFileName = MemoryUtil.memAlignedAlloc(16, 32767); + + String var4; + try { + int length = JNI.callPPI(phModule, MemoryUtil.memAddress(lpFileName), lpFileName.capacity(), PFN_GetModuleFileNameW); + if (length == 0) { + throw new RuntimeException("GetModuleFileNameW failed, error=" + getLastError()); + } + + var4 = MemoryUtil.memUTF16(lpFileName, length); + } finally { + MemoryUtil.memAlignedFree(lpFileName); + } + + return var4; + } + + public static int getLastError() { + return JNI.callI(PFN_GetLastError); + } + + static { + PFN_GetCommandLineW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetCommandLineW"); + PFN_SetEnvironmentVariableW = APIUtil.apiGetFunctionAddress(LIBRARY, "SetEnvironmentVariableW"); + PFN_GetModuleHandleExW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetModuleHandleExW"); + PFN_GetLastError = APIUtil.apiGetFunctionAddress(LIBRARY, "GetLastError"); + PFN_GetModuleFileNameW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetModuleFileNameW"); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/Libc.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/Libc.java new file mode 100644 index 0000000000..9f7683b41c --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/Libc.java @@ -0,0 +1,29 @@ +// from embeddium project +package dev.dubhe.anvilcraft.api.rendering.util.thirdparty; + +import java.nio.ByteBuffer; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.system.APIUtil; +import org.lwjgl.system.JNI; +import org.lwjgl.system.Library; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.system.SharedLibrary; + +public class Libc { + private static final SharedLibrary LIBRARY = Library.loadNative("me.jellyquid.mods.sodium", "libc.so.6"); + private static final long PFN_setenv; + + public static void setEnvironmentVariable(String name, @Nullable String value) { + try (MemoryStack stack = MemoryStack.stackPush()) { + ByteBuffer nameBuf = stack.UTF8(name); + ByteBuffer valueBuf = value != null ? stack.UTF8(value) : null; + JNI.callPPI(MemoryUtil.memAddress(nameBuf), MemoryUtil.memAddressSafe(valueBuf), 1, PFN_setenv); + } + + } + + static { + PFN_setenv = APIUtil.apiGetFunctionAddress(LIBRARY, "setenv"); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/NvidiaWorkarounds.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/NvidiaWorkarounds.java new file mode 100644 index 0000000000..20c6b06ab4 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/NvidiaWorkarounds.java @@ -0,0 +1,35 @@ +// from embeddium project +package dev.dubhe.anvilcraft.api.rendering.util.thirdparty; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Locale; + +public class NvidiaWorkarounds { + private static final Logger LOGGER = LoggerFactory.getLogger("AnvilCraft"); + + public static void install() { + LOGGER.warn("Applying workaround: Prevent the NVIDIA OpenGL driver from using broken optimizations (NVIDIA_THREADED_OPTIMIZATIONS)"); + + try { + String s = System.getProperty("os.name").toLowerCase(Locale.ROOT); + if (s.contains("win")) { + WindowsCommandLine.setCommandLine("net.caffeinemc.sodium"); + Kernel32.setEnvironmentVariable("SHIM_MCCOMPAT", "0x800000001"); + } else { + if (!s.contains("linux") && !s.contains("unix")) return; + Libc.setEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", "0"); + } + } catch (Throwable t) { + } + + } + + public static void uninstall() { + String s = System.getProperty("os.name").toLowerCase(Locale.ROOT); + if (s.contains("win")) { + WindowsCommandLine.setCommandLine("net.caffeinemc.sodium"); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/WindowsCommandLine.java b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/WindowsCommandLine.java new file mode 100644 index 0000000000..a81b801e23 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/rendering/util/thirdparty/WindowsCommandLine.java @@ -0,0 +1,61 @@ +// from embeddium project +package dev.dubhe.anvilcraft.api.rendering.util.thirdparty; + +import org.lwjgl.system.MemoryUtil; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.Objects; + +public class WindowsCommandLine { + private static CommandLineHook ACTIVE_COMMAND_LINE_HOOK; + + public static void setCommandLine(String modifiedCmdline) { + if (ACTIVE_COMMAND_LINE_HOOK != null) { + throw new IllegalStateException("Command line is already modified"); + } else { + long pCmdline = Kernel32.getCommandLine(); + String cmdline = MemoryUtil.memUTF16(pCmdline); + int cmdlineLen = MemoryUtil.memLengthUTF16(cmdline, true); + if (MemoryUtil.memLengthUTF16(modifiedCmdline, true) > cmdlineLen) { + throw new BufferOverflowException(); + } else { + ByteBuffer buffer = MemoryUtil.memByteBuffer(pCmdline, cmdlineLen); + MemoryUtil.memUTF16(modifiedCmdline, true, buffer); + if (!Objects.equals(modifiedCmdline, MemoryUtil.memUTF16(pCmdline))) { + throw new RuntimeException("Sanity check failed, the command line arguments did not appear to change"); + } else { + ACTIVE_COMMAND_LINE_HOOK = new CommandLineHook(cmdline, buffer); + } + } + } + } + + public static void resetCommandLine() { + if (ACTIVE_COMMAND_LINE_HOOK != null) { + ACTIVE_COMMAND_LINE_HOOK.uninstall(); + ACTIVE_COMMAND_LINE_HOOK = null; + } + + } + + private static class CommandLineHook { + private final String cmdline; + private final ByteBuffer cmdlineBuf; + private boolean active = true; + + private CommandLineHook(String cmdline, ByteBuffer cmdlineBuf) { + this.cmdline = cmdline; + this.cmdlineBuf = cmdlineBuf; + } + + public void uninstall() { + if (!this.active) { + throw new IllegalStateException("Hook was already uninstalled"); + } else { + MemoryUtil.memUTF16(this.cmdline, true, this.cmdlineBuf); + this.active = false; + } + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java index cd26854c2f..13bda150ec 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java @@ -2,7 +2,7 @@ import dev.dubhe.anvilcraft.AnvilCraft; import dev.dubhe.anvilcraft.api.heat.HeaterManager; -import dev.dubhe.anvilcraft.api.rendering.CacheableBERenderingPipeline; +import dev.dubhe.anvilcraft.api.rendering.pipeline.cached.CacheableBERenderingPipeline; import dev.dubhe.anvilcraft.init.block.ModBlockTags; import dev.dubhe.anvilcraft.init.entity.ModDamageTypes; import dev.dubhe.anvilcraft.init.ModHeaterInfos; @@ -50,6 +50,7 @@ public abstract class BaseLaserBlockEntity extends BlockEntity { protected HashSet irradiateSelfLaserBlockSet = new HashSet<>(); protected boolean changed = false; + @Nullable @Getter protected BlockPos irradiateBlockPos = null; @Getter diff --git a/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserRenderer.java b/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserRenderer.java index ad0b40b719..a1b5f4c765 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserRenderer.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserRenderer.java @@ -1,7 +1,7 @@ package dev.dubhe.anvilcraft.client.renderer.laser; import com.mojang.blaze3d.vertex.PoseStack; -import dev.dubhe.anvilcraft.api.rendering.CacheableBlockEntityRenderer; +import dev.dubhe.anvilcraft.api.rendering.pipeline.cached.CacheableBlockEntityRenderer; import dev.dubhe.anvilcraft.block.entity.BaseLaserBlockEntity; import net.minecraft.client.renderer.MultiBufferSource; diff --git a/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserState.java b/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserState.java index 35827209e9..4f2e94aee6 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserState.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/renderer/laser/LaserState.java @@ -8,6 +8,7 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; import java.util.function.Function; @@ -21,6 +22,7 @@ public record LaserState( TextureAtlasSprite laserAtlasSprite, TextureAtlasSprite concreteAtlasSprite ) { + @Nullable public static LaserState create(BaseLaserBlockEntity blockEntity, PoseStack poseStack) { if (blockEntity.getIrradiateBlockPos() == null) return null; Function spriteGetter = Minecraft.getInstance() diff --git a/src/main/java/dev/dubhe/anvilcraft/client/support/PowerGridSupport.java b/src/main/java/dev/dubhe/anvilcraft/client/support/PowerGridSupport.java index 7d6500954d..50e15c147d 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/support/PowerGridSupport.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/support/PowerGridSupport.java @@ -6,7 +6,7 @@ import dev.dubhe.anvilcraft.client.AnvilCraftClient; import dev.dubhe.anvilcraft.client.init.ModRenderTargets; import dev.dubhe.anvilcraft.client.init.ModRenderTypes; -import dev.dubhe.anvilcraft.client.renderer.Line; +import dev.dubhe.anvilcraft.util.Line; import dev.dubhe.anvilcraft.client.renderer.RenderState; import dev.dubhe.anvilcraft.constant.Constant; import net.minecraft.client.Minecraft; diff --git a/src/main/java/dev/dubhe/anvilcraft/client/support/RenderThunderSupport.java b/src/main/java/dev/dubhe/anvilcraft/client/support/RenderThunderSupport.java index e1e1d02155..a0f44e272c 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/support/RenderThunderSupport.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/support/RenderThunderSupport.java @@ -1,6 +1,6 @@ package dev.dubhe.anvilcraft.client.support; -import dev.dubhe.anvilcraft.client.renderer.Line; +import dev.dubhe.anvilcraft.util.Line; import net.minecraft.world.phys.Vec3; import java.util.HashSet; diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/GlDebugMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/GlDebugMixin.java new file mode 100644 index 0000000000..f80837c5df --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/GlDebugMixin.java @@ -0,0 +1,34 @@ +package dev.dubhe.anvilcraft.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.blaze3d.platform.GlDebug; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GlDebug.class) +public class GlDebugMixin { + + @WrapOperation( + method = "printDebugLog", + at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V") + ) + private static void avoidSpam( + Logger instance, + String s, + Object o, + Operation original, + @Local(argsOnly = true, index = 3) int severity + ) { + if (severity != 37190) { + return; + } + instance.error(s, o, new RuntimeException()); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/LevelChunkMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/LevelChunkMixin.java index f17d02bbbd..b3fb4eaece 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/LevelChunkMixin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/LevelChunkMixin.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.mixin; -import dev.dubhe.anvilcraft.api.rendering.CacheableBERenderingPipeline; +import com.mojang.blaze3d.systems.RenderSystem; +import dev.dubhe.anvilcraft.api.rendering.pipeline.cached.CacheableBERenderingPipeline; import dev.dubhe.anvilcraft.block.entity.BaseLaserBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntity; @@ -23,7 +24,9 @@ public abstract class LevelChunkMixin { void onBlockEntityRemoved(BlockPos pos, CallbackInfo ci) { BlockEntity be = getBlockEntity(pos); if (be instanceof BaseLaserBlockEntity laserStateAccess) { - CacheableBERenderingPipeline.getInstance().blockRemoved(laserStateAccess); + if (RenderSystem.isOnRenderThread()) { + CacheableBERenderingPipeline.getInstance().blockRemoved(laserStateAccess); + } } } } diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/LevelRendererMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/LevelRendererMixin.java index 0883c858b0..86cf91d4aa 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/LevelRendererMixin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/LevelRendererMixin.java @@ -11,7 +11,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; -import dev.dubhe.anvilcraft.api.rendering.CacheableBERenderingPipeline; +import dev.dubhe.anvilcraft.api.rendering.pipeline.cached.CacheableBERenderingPipeline; import dev.dubhe.anvilcraft.client.init.ModRenderTargets; import dev.dubhe.anvilcraft.client.init.ModShaders; import dev.dubhe.anvilcraft.client.renderer.RenderState; diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/MinecraftClientMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/MinecraftClientMixin.java index 38dfb7606a..30af0939b6 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/MinecraftClientMixin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/MinecraftClientMixin.java @@ -1,15 +1,27 @@ package dev.dubhe.anvilcraft.mixin; -import dev.dubhe.anvilcraft.api.rendering.CacheableBERenderingPipeline; +import dev.dubhe.anvilcraft.api.rendering.pipeline.cached.CacheableBERenderingPipeline; import net.minecraft.client.Minecraft; +import net.minecraft.client.main.GameConfig; import net.minecraft.client.multiplayer.ClientLevel; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import static org.lwjgl.opengl.GL11.glEnable; +import static org.lwjgl.opengl.GL43.GL_DEBUG_OUTPUT_SYNCHRONOUS; + @Mixin(Minecraft.class) abstract class MinecraftClientMixin { + @Inject( + method = "", + at = @At("TAIL") + ) + void applyWorkaround(GameConfig gameConfig, CallbackInfo ci) { + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } + @Inject( method = "updateLevelInEngines", at = @At("HEAD") diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/plugin/AnvilCraftMixinPlugin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/plugin/AnvilCraftMixinPlugin.java index 22c1752df5..88b364fd6f 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/plugin/AnvilCraftMixinPlugin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/plugin/AnvilCraftMixinPlugin.java @@ -1,5 +1,8 @@ package dev.dubhe.anvilcraft.mixin.plugin; +import dev.dubhe.anvilcraft.api.rendering.util.thirdparty.NvidiaWorkarounds; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.fml.loading.FMLLoader; import net.neoforged.fml.loading.LoadingModList; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; @@ -22,6 +25,9 @@ private boolean isLoaded(String clazz) { @Override public void onLoad(String mixinPackage) { + if (FMLEnvironment.dist.isClient()) { + NvidiaWorkarounds.install(); + } hasZetaPiston = this.isLoaded("org/violetmoon/zeta/piston/ZetaPistonStructureResolver.class"); hasReiScreen = this.isLoaded("me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.class"); hasCreate = this.isLoaded("com/simibubi/create/Create.class"); diff --git a/src/main/java/dev/dubhe/anvilcraft/client/renderer/Line.java b/src/main/java/dev/dubhe/anvilcraft/util/Line.java similarity index 97% rename from src/main/java/dev/dubhe/anvilcraft/client/renderer/Line.java rename to src/main/java/dev/dubhe/anvilcraft/util/Line.java index a3de49df27..b9375c36c8 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/renderer/Line.java +++ b/src/main/java/dev/dubhe/anvilcraft/util/Line.java @@ -1,4 +1,4 @@ -package dev.dubhe.anvilcraft.client.renderer; +package dev.dubhe.anvilcraft.util; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; diff --git a/src/main/java/dev/dubhe/anvilcraft/util/algo/MinimumSpanningTree3D.java b/src/main/java/dev/dubhe/anvilcraft/util/algo/MinimumSpanningTree3D.java new file mode 100644 index 0000000000..6873ed4262 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/util/algo/MinimumSpanningTree3D.java @@ -0,0 +1,68 @@ +package dev.dubhe.anvilcraft.util.algo; + +import dev.dubhe.anvilcraft.util.Line; +import net.minecraft.world.phys.Vec3; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MinimumSpanningTree3D { + + private static class Edge implements Comparable { + int u, v; + double weight; + Edge(int u, int v, double weight) { this.u = u; this.v = v; this.weight = weight; } + @Override + public int compareTo(Edge o) { return Double.compare(this.weight, o.weight); } + } + + private static class UnionFind { + int[] parent, rank; + UnionFind(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) parent[i] = i; + } + int find(int x) { + if (parent[x] != x) parent[x] = find(parent[x]); + return parent[x]; + } + void union(int x, int y) { + int xr = find(x), yr = find(y); + if (xr == yr) return; + if (rank[xr] < rank[yr]) parent[xr] = yr; + else { + parent[yr] = xr; + if (rank[xr] == rank[yr]) rank[xr]++; + } + } + } + + public static List kruskalMST(List points) { + int n = points.size(); + List edges = new ArrayList<>(); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + edges.add(new Edge(i, j, distance(points.get(i), points.get(j)))); + } + } + Collections.sort(edges); + UnionFind uf = new UnionFind(n); + List result = new ArrayList<>(); + for (Edge e : edges) { + int pu = uf.find(e.u), pv = uf.find(e.v); + if (pu != pv) { + uf.union(pu, pv); + result.add(new Line(points.get(e.u), points.get(e.v), (float) e.weight)); + if (result.size() == n - 1) break; + } + } + return result; + } + + private static double distance(Vec3 a, Vec3 b) { + double dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } +} \ No newline at end of file diff --git a/src/main/resources/anvilcraft.mixins.json b/src/main/resources/anvilcraft.mixins.json index aa3f7a9aa8..2daf4172b5 100644 --- a/src/main/resources/anvilcraft.mixins.json +++ b/src/main/resources/anvilcraft.mixins.json @@ -81,6 +81,7 @@ "ClientLevelMixin", "ClientPacketListenerMixin", "GameRendererMixin", + "GlDebugMixin", "GuiGraphicsMixin", "ItemFrameRendererMixin", "ItemInHandRendererMixin",