From 184f6db9df8b3e49ff01bdc3dd9842306fdbc675 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Tue, 16 Dec 2025 20:57:40 -0500 Subject: [PATCH 1/8] Optimize BoxRenderer --- .../client/rendering/BoxRenderer.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/client/rendering/BoxRenderer.java b/src/main/java/com/recursive_pineapple/matter_manipulator/client/rendering/BoxRenderer.java index cddbb6e1..b5b840ae 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/client/rendering/BoxRenderer.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/client/rendering/BoxRenderer.java @@ -25,6 +25,8 @@ public class BoxRenderer { private final ShaderProgram program; private final int time_location; + private final VertexBuffer buffer = new VertexBuffer(DefaultVertexFormat.POSITION_COLOR_TEXTURE, GL11.GL_QUADS); + public BoxRenderer() { program = new ShaderProgram( Mods.MatterManipulator.resourceDomain, @@ -39,8 +41,6 @@ public BoxRenderer() { /** * Starts rendering fancy boxes. Should only be called once per frame, to allow quad sorting. - * - * @param partialTickTime */ public void start(double partialTickTime) { TessellatorManager.startCapturing(); @@ -163,13 +163,10 @@ public void finish() { program.use(); - // this should only be done once a frame, but there aren't any side effects from calling it more GL20.glUniform1f(time_location, (((float) (System.currentTimeMillis() % 2500)) / 1000f)); - try (VertexBuffer buffer = new VertexBuffer(DefaultVertexFormat.POSITION_COLOR_TEXTURE, GL11.GL_QUADS);) { - buffer.upload(bytes); - buffer.render(); - } + buffer.upload(bytes); + buffer.render(); ShaderProgram.clear(); From 11be7aeed493cff4407c7e2ecfd238c267536c68 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Tue, 16 Dec 2025 21:02:36 -0500 Subject: [PATCH 2/8] VBO improvements + use SharedDrawable --- .../common/items/manipulator/RenderHints.java | 125 +++++++++++------- 1 file changed, 75 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java index 3cd7a838..b63c96db 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java @@ -32,8 +32,11 @@ import org.joml.Vector3d; import org.joml.Vector3i; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.SharedDrawable; import org.lwjgl.util.glu.GLU; import lombok.Setter; @@ -41,19 +44,34 @@ @EventBusSubscriber(side = Side.CLIENT) public class RenderHints { - private static final int BYTES_PER_HINT = DefaultVertexFormat.POSITION_TEXTURE_COLOR.getVertexSize() * 4 * 6; - + /// The latest list of hints. This is not sorted in any way and can only be accessed by the main thread. private static final ArrayList HINTS = new ArrayList<>(10000); + /// The list of hints that was sent to the worker thread. This field is modified by the main thread but the list + /// itself is modified by the worker thread. This is an optimization because the list from the prior position is + /// almost certainly nearly-sorted, which should reduce the number of comparisons significantly. + private static ArrayList drawnHints = null; + private static final Vector3d LAST_PLAYER_POSITION = new Vector3d(); private static final Vector3i LAST_RENDERED_PLAYER_POSITION = new Vector3i(); private static boolean vboNeedsRebuild = false; - /** The VBO being used for rendering */ + /// The VBO that's being used for rendering private static VertexBuffer activeVBO; - /** The VBO that's mapped and is being written to */ + /// The VBO that's being written to by the worker thread (or is idle) private static VertexBuffer pendingVBO; + /// An opengl context that's active on the background thread and is used for writing to the pending VBO. + private static final SharedDrawable BACKGROUND_CONTEXT; + + static { + try { + BACKGROUND_CONTEXT = new SharedDrawable(Display.getDrawable()); + } catch (LWJGLException e) { + throw new RuntimeException("Could not initialized background SharedDrawable", e); + } + } + private static final ExecutorService WORKER_THREAD = Executors.newFixedThreadPool(1); private static Future renderTask; @@ -67,12 +85,12 @@ public static void reset() { } HINTS.clear(); + drawnHints = null; vboNeedsRebuild = true; } public static void addHint(int x, int y, int z, Block block, int meta, short[] tint) { - Hint hint = new Hint(); hint.x = x; @@ -86,6 +104,9 @@ public static void addHint(int x, int y, int z, Block block, int meta, short[] t } HINTS.add(hint); + + // Invalidate the cached sort results + drawnHints = null; } @SubscribeEvent @@ -95,48 +116,62 @@ public static void onWorldLoad(WorldEvent.Load e) { } } - private static VBOResult buildVBO(ByteBuffer buffer, ArrayList hints, double xd, double yd, double zd, int xi, int yi, int zi) { + private static VBOResult buildVBO(VertexBuffer vbo, ArrayList hints, double xd, double yd, double zd, int xi, int yi, int zi) { try { Vector3d eyes = new Vector3d(xd, yd, zd); - hints.sort(Comparator.comparingDouble(info -> -eyes.distanceSquared(info.x + 0.5, info.y + 0.5, info.z + 0.5))); + try { + BACKGROUND_CONTEXT.makeCurrent(); + } catch (LWJGLException e) { + throw new RuntimeException("Could not activate background GL context", e); + } - TessellatorManager.startCapturing(); + hints.sort(Comparator.comparingDouble(info -> -eyes.distanceSquared(info.x + 0.5, info.y + 0.5, info.z + 0.5))); - Tessellator tes = TessellatorManager.get(); + Tessellator tes = TessellatorManager.startCapturingAndGet(); tes.startDrawing(GL11.GL_QUADS); int hintCount = hints.size(); - // noinspection ForLoopReplaceableByForEach for (int i = 0; i < hintCount; i++) { hints.get(i).draw(tes, xd, yd, zd, xi, yi, zi); } final var quads = TessellatorManager.stopCapturingToPooledQuads(); - long expectedSize = (long) DefaultVertexFormat.POSITION_TEXTURE_COLOR.getVertexSize() * quads.size() * 4; + final VertexFormat format = DefaultVertexFormat.POSITION_TEXTURE_COLOR; + + long bufferSize = (long) format.getVertexSize() * quads.size() * 4; + + ByteBuffer buffer = vbo.map(GL15.GL_WRITE_ONLY, bufferSize, GL15.GL_STREAM_DRAW); buffer.rewind(); - if (expectedSize > buffer.capacity()) { + if (bufferSize > buffer.capacity()) { MMMod.LOG.error( - "Could not upload hint VBO: Could not insert hint quads into GL buffer (expectedSize={}, buffer.capacity={})", - expectedSize, + "Could not upload hint VBO: Could not insert hint quads into GL buffer (bufferSize={}, buffer.capacity={})", + bufferSize, buffer.capacity() ); return new VBOResult(new Vector3i(xi, yi, zi), 0); } - // noinspection ForLoopReplaceableByForEach for (int i = 0, quadsSize = quads.size(); i < quadsSize; i++) { - DefaultVertexFormat.POSITION_TEXTURE_COLOR.writeQuad(quads.get(i), buffer); + format.writeQuad(quads.get(i), buffer); } buffer.rewind(); + vbo.unmap(); + + try { + BACKGROUND_CONTEXT.releaseContext(); + } catch (LWJGLException e) { + throw new RuntimeException("Could not release background GL context", e); + } + return new VBOResult(new Vector3i(xi, yi, zi), quads.size() * 4); } finally { TessellatorManager.cleanup(); @@ -169,14 +204,14 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { if (renderTask != null && renderTask.isDone()) { VBOResult result = null; + try { result = renderTask.get(); } catch (InterruptedException | ExecutionException ex) { - MMMod.LOG.error("Could not cancel render hints", ex); + MMMod.LOG.error("Could not assemble render hint quads", ex); } renderTask = null; - pendingVBO.unmap(); if (result != null) { LAST_RENDERED_PLAYER_POSITION.set(result.playerPosition); @@ -192,16 +227,13 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { LAST_PLAYER_POSITION.set(currentPos); vboNeedsRebuild = false; - ArrayList hints = new ArrayList<>(HINTS); - - if (pendingVBO.mapped) pendingVBO.unmap(); - - pendingVBO.ensureSize((long) hints.size() * BYTES_PER_HINT, GL15.GL_STREAM_DRAW); - ByteBuffer buffer = pendingVBO.map(GL15.GL_WRITE_ONLY); - - if (buffer != null) { - renderTask = WORKER_THREAD.submit(() -> buildVBO(buffer, hints, xd, yd, zd, xi, yi, zi)); + // If the hint list has changed, re-copy them into the drawnHints list so that the worker thread can sort + // them. + if (drawnHints == null) { + drawnHints = new ArrayList<>(HINTS); } + + renderTask = WORKER_THREAD.submit(() -> buildVBO(pendingVBO, drawnHints, xd, yd, zd, xi, yi, zi)); } if (activeVBO.vertexCount > 0) { @@ -355,10 +387,10 @@ public VBOResult(Vector3i playerPosition, int vertexCount) { private static class VertexBuffer implements AutoCloseable { - private int id; + private volatile int id; private volatile int vertexCount; - private VertexFormat format; - private int drawMode; + private volatile VertexFormat format; + private volatile int drawMode; private volatile long currentSize; private volatile int currentUsage; @@ -384,7 +416,7 @@ public void unbind() { } public void upload(int usage, ByteBuffer buffer, int vertexCount) { - if (this.id != -1) { + if (this.id > 0) { this.vertexCount = vertexCount; this.bind(); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, usage); @@ -401,9 +433,9 @@ public void upload(ByteBuffer buffer) { } public void close() { - if (this.id >= 0) { + if (this.id > 0) { GL15.glDeleteBuffers(this.id); - this.id = -1; + this.id = 0; } } @@ -416,6 +448,8 @@ public void draw(FloatBuffer floatBuffer) { } public void draw() { + if (mapped) throw new IllegalStateException("Cannot draw a buffer that is mapped"); + GL11.glDrawArrays(this.drawMode, 0, this.vertexCount); } @@ -439,26 +473,19 @@ public void render() { this.cleanupState(); } - public void ensureSize(long size, int usage) { - if (size > currentSize || currentSize / 4 > size || currentUsage != usage) { - bind(); - - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, size, usage); - currentSize = size; - currentUsage = usage; - - unbind(); - } - } - @SuppressWarnings("NonAtomicOperationOnVolatileField") - public ByteBuffer map(int access) { + public ByteBuffer map(int access, long size, int usage) { if (mapped) throw new IllegalStateException("cannot map the same buffer twice"); - if (currentSize == 0) throw new IllegalStateException("cannot map an empty buffer"); bind(); - GL11.glGetError(); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, size, usage); + currentSize = size; + currentUsage = usage; + + if (oldMap != null) { + oldMap.clear(); + } oldMap = GL15.glMapBuffer(GL15.GL_ARRAY_BUFFER, access, currentSize, oldMap); @@ -478,8 +505,6 @@ public void unmap() { bind(); - GL11.glGetError(); - GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); int error = GL11.glGetError(); From a815cb3d5e04bde893d42fc4f52adcb7f33cd0b3 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 17 Dec 2025 15:35:49 -0500 Subject: [PATCH 3/8] Pull VertexBuffer into its own class and improve it --- dependencies.gradle | 4 +- .../manipulator/FixedLengthVertexBuffer.java | 80 +++++++ .../common/items/manipulator/RenderHints.java | 163 ++----------- .../manipulator/StreamingVertexBuffer.java | 216 ++++++++++++++++++ 4 files changed, 312 insertions(+), 151 deletions(-) create mode 100644 src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/FixedLengthVertexBuffer.java create mode 100644 src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java diff --git a/dependencies.gradle b/dependencies.gradle index 37fece01..311c8f29 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -73,7 +73,7 @@ dependencies { runtimeOnlyNonPublishable("com.github.GTNewHorizons:Avaritia:1.78:dev") runtimeOnlyNonPublishable("com.github.GTNewHorizons:Avaritiaddons:1.9.3-GTNH:dev") runtimeOnlyNonPublishable("com.github.GTNewHorizons:DuraDisplay:1.4.0:dev") - runtimeOnlyNonPublishable('com.github.GTNewHorizons:EnderIO:2.10.8:dev') +// runtimeOnlyNonPublishable('com.github.GTNewHorizons:EnderIO:2.10.8:dev') runtimeOnlyNonPublishable("com.github.GTNewHorizons:EnderStorage:1.8.0:dev") runtimeOnlyNonPublishable("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.140:dev") runtimeOnlyNonPublishable("com.github.GTNewHorizons:FloodLights:1.5.5:dev") @@ -95,8 +95,6 @@ dependencies { testImplementation(platform('org.junit:junit-bom:5.9.2')) testImplementation('org.junit.jupiter:junit-jupiter') - - runtimeOnlyNonPublishable(rfg.deobf("curse.maven:spark-361579:4271867")) } // deps may transitively add Baubles, so we replace it diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/FixedLengthVertexBuffer.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/FixedLengthVertexBuffer.java new file mode 100644 index 00000000..86407b66 --- /dev/null +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/FixedLengthVertexBuffer.java @@ -0,0 +1,80 @@ +package com.recursive_pineapple.matter_manipulator.common.items.manipulator; + +import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat; + +import org.intellij.lang.annotations.MagicConstant; +import org.lwjgl.opengl.ARBBufferStorage; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; + +/// Note: this doesn't work properly for some reason, don't use it as-is +public class FixedLengthVertexBuffer extends StreamingVertexBuffer { + + public FixedLengthVertexBuffer(VertexFormat format, int drawMode) { + super(format, drawMode); + } + + @Override + public void reallocate() { + generate(); + + // noinspection MagicConstant + setSize(this.length, this.bufferFlags); + } + + @Override + public void allocate( + int vertexCount, + @MagicConstant(intValues = { + ARBBufferStorage.GL_DYNAMIC_STORAGE_BIT, + GL30.GL_MAP_READ_BIT, + GL30.GL_MAP_WRITE_BIT, + ARBBufferStorage.GL_MAP_PERSISTENT_BIT, + ARBBufferStorage.GL_MAP_COHERENT_BIT, + ARBBufferStorage.GL_CLIENT_STORAGE_BIT, + }) int usage + ) { + // noinspection MagicConstant + if (this.id == 0 || vertexCount < this.vertexCount / 4 || vertexCount > this.vertexCount || usage != this.bufferFlags) { + generate(); + + setSize(vertexCount * (long) format.getVertexSize(), usage); + } + + this.vertexCount = vertexCount; + } + + public void setSize( + long length, + @MagicConstant(intValues = { + ARBBufferStorage.GL_DYNAMIC_STORAGE_BIT, + GL30.GL_MAP_READ_BIT, + GL30.GL_MAP_WRITE_BIT, + ARBBufferStorage.GL_MAP_PERSISTENT_BIT, + ARBBufferStorage.GL_MAP_COHERENT_BIT, + ARBBufferStorage.GL_CLIENT_STORAGE_BIT, + }) int bufferFlags + ) { + if (this.length > 0) throw new IllegalStateException("Cannot resize an immutable (fixed length) vertex buffer"); + + bind(); + + this.length = length; + this.bufferFlags = bufferFlags; + ARBBufferStorage.glBufferStorage(GL15.GL_ARRAY_BUFFER, length, bufferFlags); + + unbind(); + } + + public void flush() { + bind(); + GL30.glFlushMappedBufferRange(GL15.GL_ARRAY_BUFFER, 0, vertexCount * (long) format.getVertexSize()); + unbind(); + } + + public void flushAll() { + bind(); + GL30.glFlushMappedBufferRange(GL15.GL_ARRAY_BUFFER, 0, length); + unbind(); + } +} diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java index b63c96db..7fb90708 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java @@ -1,7 +1,6 @@ package com.recursive_pineapple.matter_manipulator.common.items.manipulator; import java.nio.ByteBuffer; -import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Comparator; import java.util.concurrent.ExecutionException; @@ -36,8 +35,8 @@ import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.SharedDrawable; -import org.lwjgl.util.glu.GLU; import lombok.Setter; @@ -57,9 +56,9 @@ public class RenderHints { private static boolean vboNeedsRebuild = false; /// The VBO that's being used for rendering - private static VertexBuffer activeVBO; + private static StreamingVertexBuffer activeVBO; /// The VBO that's being written to by the worker thread (or is idle) - private static VertexBuffer pendingVBO; + private static StreamingVertexBuffer pendingVBO; /// An opengl context that's active on the background thread and is used for writing to the pending VBO. private static final SharedDrawable BACKGROUND_CONTEXT; @@ -116,7 +115,7 @@ public static void onWorldLoad(WorldEvent.Load e) { } } - private static VBOResult buildVBO(VertexBuffer vbo, ArrayList hints, double xd, double yd, double zd, int xi, int yi, int zi) { + private static VBOResult buildVBO(StreamingVertexBuffer vbo, ArrayList hints, double xd, double yd, double zd, int xi, int yi, int zi) { try { Vector3d eyes = new Vector3d(xd, yd, zd); @@ -142,16 +141,18 @@ private static VBOResult buildVBO(VertexBuffer vbo, ArrayList hints, doubl final VertexFormat format = DefaultVertexFormat.POSITION_TEXTURE_COLOR; - long bufferSize = (long) format.getVertexSize() * quads.size() * 4; + vbo.allocate(quads.size() * 4, GL15.GL_STREAM_DRAW); - ByteBuffer buffer = vbo.map(GL15.GL_WRITE_ONLY, bufferSize, GL15.GL_STREAM_DRAW); + ByteBuffer buffer = vbo.map(GL30.GL_MAP_WRITE_BIT); buffer.rewind(); - if (bufferSize > buffer.capacity()) { + long expectedSize = (long) format.getVertexSize() * quads.size() * 4; + + if (expectedSize > buffer.capacity()) { MMMod.LOG.error( - "Could not upload hint VBO: Could not insert hint quads into GL buffer (bufferSize={}, buffer.capacity={})", - bufferSize, + "Could not upload hint VBO: Could not insert hint quads into GL buffer (expectedSize={}, buffer.capacity={})", + expectedSize, buffer.capacity() ); @@ -195,11 +196,11 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { Vector3d currentPos = new Vector3d(xd, yd, zd); if (activeVBO == null) { - activeVBO = new VertexBuffer(DefaultVertexFormat.POSITION_TEXTURE_COLOR, GL11.GL_QUADS); + activeVBO = new StreamingVertexBuffer(DefaultVertexFormat.POSITION_TEXTURE_COLOR, GL11.GL_QUADS); } if (pendingVBO == null) { - pendingVBO = new VertexBuffer(DefaultVertexFormat.POSITION_TEXTURE_COLOR, GL11.GL_QUADS); + pendingVBO = new StreamingVertexBuffer(DefaultVertexFormat.POSITION_TEXTURE_COLOR, GL11.GL_QUADS); } if (renderTask != null && renderTask.isDone()) { @@ -215,9 +216,8 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { if (result != null) { LAST_RENDERED_PLAYER_POSITION.set(result.playerPosition); - pendingVBO.vertexCount = result.vertexCount; - VertexBuffer temp = activeVBO; + StreamingVertexBuffer temp = activeVBO; activeVBO = pendingVBO; pendingVBO = temp; } @@ -236,7 +236,7 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { renderTask = WORKER_THREAD.submit(() -> buildVBO(pendingVBO, drawnHints, xd, yd, zd, xi, yi, zi)); } - if (activeVBO.vertexCount > 0) { + if (activeVBO.getVertexCount() > 0) { p.startSection("Draw MM Hints"); GL11.glPushMatrix(); @@ -384,137 +384,4 @@ public VBOResult(Vector3i playerPosition, int vertexCount) { this.vertexCount = vertexCount; } } - - private static class VertexBuffer implements AutoCloseable { - - private volatile int id; - private volatile int vertexCount; - private volatile VertexFormat format; - private volatile int drawMode; - - private volatile long currentSize; - private volatile int currentUsage; - private volatile ByteBuffer oldMap; - private volatile boolean mapped; - - public VertexBuffer() { - this.id = GL15.glGenBuffers(); - } - - public VertexBuffer(VertexFormat format, int drawMode) { - this(); - this.format = format; - this.drawMode = drawMode; - } - - public void bind() { - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.id); - } - - public void unbind() { - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); - } - - public void upload(int usage, ByteBuffer buffer, int vertexCount) { - if (this.id > 0) { - this.vertexCount = vertexCount; - this.bind(); - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, usage); - this.unbind(); - } - } - - public void upload(ByteBuffer buffer) { - if (this.format == null) { - throw new IllegalStateException("No format specified for VBO upload"); - } else { - this.upload(GL15.GL_STATIC_DRAW, buffer, buffer.remaining() / this.format.getVertexSize()); - } - } - - public void close() { - if (this.id > 0) { - GL15.glDeleteBuffers(this.id); - this.id = 0; - } - } - - public void draw(FloatBuffer floatBuffer) { - GL11.glPushMatrix(); - GL11.glLoadIdentity(); - GL11.glMultMatrix(floatBuffer); - this.draw(); - GL11.glPopMatrix(); - } - - public void draw() { - if (mapped) throw new IllegalStateException("Cannot draw a buffer that is mapped"); - - GL11.glDrawArrays(this.drawMode, 0, this.vertexCount); - } - - public void setupState() { - if (this.format == null) { - throw new IllegalStateException("No format specified for VBO setup"); - } else { - this.bind(); - this.format.setupBufferState(0L); - } - } - - public void cleanupState() { - this.format.clearBufferState(); - this.unbind(); - } - - public void render() { - this.setupState(); - this.draw(); - this.cleanupState(); - } - - @SuppressWarnings("NonAtomicOperationOnVolatileField") - public ByteBuffer map(int access, long size, int usage) { - if (mapped) throw new IllegalStateException("cannot map the same buffer twice"); - - bind(); - - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, size, usage); - currentSize = size; - currentUsage = usage; - - if (oldMap != null) { - oldMap.clear(); - } - - oldMap = GL15.glMapBuffer(GL15.GL_ARRAY_BUFFER, access, currentSize, oldMap); - - if (oldMap == null) { - MMMod.LOG.error("Error mapping buffer: {}", GLU.gluErrorString(GL11.glGetError())); - } else { - mapped = true; - } - - unbind(); - - return oldMap; - } - - public void unmap() { - if (!mapped) throw new IllegalStateException("cannot unmap the same buffer twice"); - - bind(); - - GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); - int error = GL11.glGetError(); - - if (error != 0) { - MMMod.LOG.error("Error unmapping buffer: {}", GLU.gluErrorString(error)); - } - - mapped = false; - - unbind(); - } - } } diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java new file mode 100644 index 00000000..747aceef --- /dev/null +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java @@ -0,0 +1,216 @@ +package com.recursive_pineapple.matter_manipulator.common.items.manipulator; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat; +import com.recursive_pineapple.matter_manipulator.MMMod; + +import org.intellij.lang.annotations.MagicConstant; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.util.glu.GLU; + +import lombok.Getter; + +public class StreamingVertexBuffer implements AutoCloseable { + + @Getter + protected final VertexFormat format; + @Getter + protected final int drawMode; + + @Getter + protected volatile int id; + @Getter + protected volatile int vertexCount; + + @Getter + protected volatile long length; + @Getter + protected volatile int bufferFlags; + + @Getter + protected volatile ByteBuffer mappedBuffer; + @Getter + protected volatile boolean mapped; + + public StreamingVertexBuffer(VertexFormat format, int drawMode) { + this.id = GL15.glGenBuffers(); + this.format = format; + this.drawMode = drawMode; + } + + /// Generates a new vertex buffer and closes the previous one, if present. + /// This should be used sparingly - it's very expensive to call it each frame. + public void generate() { + if (this.id > 0) { + close(); + } + + this.id = GL15.glGenBuffers(); + } + + @Override + public void close() { + if (this.id > 0) { + if (mapped) unmap(); + + GL15.glDeleteBuffers(this.id); + + this.id = 0; + this.vertexCount = 0; + this.length = 0; + } + } + + public void bind() { + if (this.id == 0) throw new IllegalStateException("Cannot bind unallocated VBO"); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.id); + } + + public void unbind() { + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + } + + public void upload(int usage, ByteBuffer buffer, int vertexCount) { + if (this.id > 0) { + this.vertexCount = vertexCount; + this.bind(); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, usage); + this.unbind(); + } + } + + public void upload(ByteBuffer buffer) { + this.upload(GL15.GL_STATIC_DRAW, buffer, buffer.remaining() / this.format.getVertexSize()); + } + + public void draw(FloatBuffer floatBuffer) { + GL11.glPushMatrix(); + GL11.glLoadIdentity(); + GL11.glMultMatrix(floatBuffer); + this.draw(); + GL11.glPopMatrix(); + } + + public void draw() { + if (mapped) throw new IllegalStateException("Cannot draw a buffer that is mapped"); + + GL11.glDrawArrays(this.drawMode, 0, this.vertexCount); + } + + public void setupState() { + this.bind(); + this.format.setupBufferState(0L); + } + + public void cleanupState() { + this.format.clearBufferState(); + this.unbind(); + } + + public void render() { + this.setupState(); + this.draw(); + this.cleanupState(); + } + + /// Reallocates the memory stored in this VBO so that the driver can avoid synchronization flushes. Typically + /// drivers pool memory so there's a good chance it'll just pull it from the pool instead of allocating anything + /// since the length is the same. + /// Reference: [Buffer Object Streaming](https://wikis.khronos.org/opengl/Buffer_Object_Streaming) + public void reallocate() { + bind(); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, length, bufferFlags); + unbind(); + } + + public void allocate( + int vertexCount, + @MagicConstant(intValues = { + GL15.GL_STREAM_DRAW, + GL15.GL_STREAM_READ, + GL15.GL_STREAM_COPY, + GL15.GL_STATIC_DRAW, + GL15.GL_STATIC_READ, + GL15.GL_STATIC_COPY, + GL15.GL_DYNAMIC_DRAW, + GL15.GL_DYNAMIC_READ, + GL15.GL_DYNAMIC_COPY, + }) int usage + ) { + bind(); + + this.vertexCount = vertexCount; + this.length = vertexCount * (long) format.getVertexSize(); + this.bufferFlags = usage; + + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.length, this.usage); + + unbind(); + } + + /// Maps the buffer into the client memory space (CPU) and returns a [ByteBuffer] wrapper for it. + /// @param access See [glMapBufferRange](https://docs.gl/es3/glMapBufferRange) for more info. + @SuppressWarnings("NonAtomicOperationOnVolatileField") + public ByteBuffer map( + @MagicConstant(intValues = { + GL30.GL_MAP_READ_BIT, + GL30.GL_MAP_WRITE_BIT, + GL30.GL_MAP_INVALIDATE_RANGE_BIT, + GL30.GL_MAP_INVALIDATE_BUFFER_BIT, + GL30.GL_MAP_FLUSH_EXPLICIT_BIT, + GL30.GL_MAP_UNSYNCHRONIZED_BIT + }) int access + ) { + if (mapped) throw new IllegalStateException("cannot map the same buffer twice"); + + bind(); + + if (mappedBuffer != null) { + mappedBuffer.clear(); + } + + mappedBuffer = GL30.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, length, access, mappedBuffer); + + if (mappedBuffer == null) { + MMMod.LOG.error("Error mapping buffer: {}", GLU.gluErrorString(GL11.glGetError())); + } else { + mapped = true; + } + + unbind(); + + return mappedBuffer; + } + + @SuppressWarnings("UnusedReturnValue") + public boolean unmap() { + if (!mapped) throw new IllegalStateException("cannot unmap the same buffer twice"); + + bind(); + + boolean valid = true; + + if (!GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER)) { + // Something happened that corrupted the VBO, it has to be re-initialized + reallocate(); + valid = false; + } + + int error = GL11.glGetError(); + + if (error != 0) { + MMMod.LOG.error("Error unmapping buffer: {}", GLU.gluErrorString(error)); + } + + mapped = false; + + unbind(); + + return valid; + } +} From d335843d34626f581a5d734d9f71b83d93bc2d35 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 17 Dec 2025 15:49:12 -0500 Subject: [PATCH 4/8] oops --- .../common/items/manipulator/StreamingVertexBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java index 747aceef..41c9fa80 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java @@ -148,7 +148,7 @@ public void allocate( this.length = vertexCount * (long) format.getVertexSize(); this.bufferFlags = usage; - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.length, this.usage); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.length, this.bufferFlags); unbind(); } From d4ba2a54b60ed4b247e71074fd7376dd2c2e45d8 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 17 Dec 2025 16:37:57 -0500 Subject: [PATCH 5/8] Make context persistent --- .../common/items/manipulator/RenderHints.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java index 7fb90708..51bc89da 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java @@ -17,6 +17,7 @@ import net.minecraft.util.IIcon; import net.minecraft.world.World; +import net.minecraftforge.client.event.RenderWorldEvent; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.event.world.WorldEvent; @@ -120,7 +121,9 @@ private static VBOResult buildVBO(StreamingVertexBuffer vbo, ArrayList hin Vector3d eyes = new Vector3d(xd, yd, zd); try { - BACKGROUND_CONTEXT.makeCurrent(); + if (!BACKGROUND_CONTEXT.isCurrent()) { + BACKGROUND_CONTEXT.makeCurrent(); + } } catch (LWJGLException e) { throw new RuntimeException("Could not activate background GL context", e); } @@ -167,12 +170,6 @@ private static VBOResult buildVBO(StreamingVertexBuffer vbo, ArrayList hin vbo.unmap(); - try { - BACKGROUND_CONTEXT.releaseContext(); - } catch (LWJGLException e) { - throw new RuntimeException("Could not release background GL context", e); - } - return new VBOResult(new Vector3i(xi, yi, zi), quads.size() * 4); } finally { TessellatorManager.cleanup(); From 183b16b2548acac62753747ac61550d5185804a3 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 17 Dec 2025 16:38:09 -0500 Subject: [PATCH 6/8] volatile -> synchronized --- .../manipulator/StreamingVertexBuffer.java | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java index 41c9fa80..13325d5c 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java @@ -22,19 +22,19 @@ public class StreamingVertexBuffer implements AutoCloseable { protected final int drawMode; @Getter - protected volatile int id; + protected int id; @Getter - protected volatile int vertexCount; + protected int vertexCount; @Getter - protected volatile long length; + protected long length; @Getter - protected volatile int bufferFlags; + protected int bufferFlags; @Getter - protected volatile ByteBuffer mappedBuffer; + protected ByteBuffer mappedBuffer; @Getter - protected volatile boolean mapped; + protected boolean mapped; public StreamingVertexBuffer(VertexFormat format, int drawMode) { this.id = GL15.glGenBuffers(); @@ -44,7 +44,7 @@ public StreamingVertexBuffer(VertexFormat format, int drawMode) { /// Generates a new vertex buffer and closes the previous one, if present. /// This should be used sparingly - it's very expensive to call it each frame. - public void generate() { + public synchronized void generate() { if (this.id > 0) { close(); } @@ -53,7 +53,7 @@ public void generate() { } @Override - public void close() { + public synchronized void close() { if (this.id > 0) { if (mapped) unmap(); @@ -65,17 +65,17 @@ public void close() { } } - public void bind() { + public synchronized void bind() { if (this.id == 0) throw new IllegalStateException("Cannot bind unallocated VBO"); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.id); } - public void unbind() { + public synchronized void unbind() { GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); } - public void upload(int usage, ByteBuffer buffer, int vertexCount) { + public synchronized void upload(int usage, ByteBuffer buffer, int vertexCount) { if (this.id > 0) { this.vertexCount = vertexCount; this.bind(); @@ -84,11 +84,11 @@ public void upload(int usage, ByteBuffer buffer, int vertexCount) { } } - public void upload(ByteBuffer buffer) { + public synchronized void upload(ByteBuffer buffer) { this.upload(GL15.GL_STATIC_DRAW, buffer, buffer.remaining() / this.format.getVertexSize()); } - public void draw(FloatBuffer floatBuffer) { + public synchronized void draw(FloatBuffer floatBuffer) { GL11.glPushMatrix(); GL11.glLoadIdentity(); GL11.glMultMatrix(floatBuffer); @@ -96,23 +96,23 @@ public void draw(FloatBuffer floatBuffer) { GL11.glPopMatrix(); } - public void draw() { + public synchronized void draw() { if (mapped) throw new IllegalStateException("Cannot draw a buffer that is mapped"); GL11.glDrawArrays(this.drawMode, 0, this.vertexCount); } - public void setupState() { + public synchronized void setupState() { this.bind(); this.format.setupBufferState(0L); } - public void cleanupState() { + public synchronized void cleanupState() { this.format.clearBufferState(); this.unbind(); } - public void render() { + public synchronized void render() { this.setupState(); this.draw(); this.cleanupState(); @@ -122,13 +122,13 @@ public void render() { /// drivers pool memory so there's a good chance it'll just pull it from the pool instead of allocating anything /// since the length is the same. /// Reference: [Buffer Object Streaming](https://wikis.khronos.org/opengl/Buffer_Object_Streaming) - public void reallocate() { + public synchronized void reallocate() { bind(); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, length, bufferFlags); unbind(); } - public void allocate( + public synchronized void allocate( int vertexCount, @MagicConstant(intValues = { GL15.GL_STREAM_DRAW, @@ -155,8 +155,7 @@ public void allocate( /// Maps the buffer into the client memory space (CPU) and returns a [ByteBuffer] wrapper for it. /// @param access See [glMapBufferRange](https://docs.gl/es3/glMapBufferRange) for more info. - @SuppressWarnings("NonAtomicOperationOnVolatileField") - public ByteBuffer map( + public synchronized ByteBuffer map( @MagicConstant(intValues = { GL30.GL_MAP_READ_BIT, GL30.GL_MAP_WRITE_BIT, @@ -188,7 +187,7 @@ public ByteBuffer map( } @SuppressWarnings("UnusedReturnValue") - public boolean unmap() { + public synchronized boolean unmap() { if (!mapped) throw new IllegalStateException("cannot unmap the same buffer twice"); bind(); From 910997ba1235955a1c9ad03c404304085e28258d Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 17 Dec 2025 17:50:43 -0500 Subject: [PATCH 7/8] Proper synchronization --- .../common/items/manipulator/RenderHints.java | 44 +++++++++++-------- .../manipulator/StreamingVertexBuffer.java | 30 ++++++------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java index 51bc89da..756154ec 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java @@ -144,31 +144,34 @@ private static VBOResult buildVBO(StreamingVertexBuffer vbo, ArrayList hin final VertexFormat format = DefaultVertexFormat.POSITION_TEXTURE_COLOR; - vbo.allocate(quads.size() * 4, GL15.GL_STREAM_DRAW); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (vbo) { + vbo.allocate(quads.size() * 4, GL15.GL_STREAM_DRAW); - ByteBuffer buffer = vbo.map(GL30.GL_MAP_WRITE_BIT); + ByteBuffer buffer = vbo.map(GL30.GL_MAP_WRITE_BIT); - buffer.rewind(); + buffer.rewind(); - long expectedSize = (long) format.getVertexSize() * quads.size() * 4; + long expectedSize = (long) format.getVertexSize() * quads.size() * 4; - if (expectedSize > buffer.capacity()) { - MMMod.LOG.error( - "Could not upload hint VBO: Could not insert hint quads into GL buffer (expectedSize={}, buffer.capacity={})", - expectedSize, - buffer.capacity() - ); + if (expectedSize > buffer.capacity()) { + MMMod.LOG.error( + "Could not upload hint VBO: Could not insert hint quads into GL buffer (expectedSize={}, buffer.capacity={})", + expectedSize, + buffer.capacity() + ); - return new VBOResult(new Vector3i(xi, yi, zi), 0); - } + return new VBOResult(new Vector3i(xi, yi, zi), 0); + } - for (int i = 0, quadsSize = quads.size(); i < quadsSize; i++) { - format.writeQuad(quads.get(i), buffer); - } + for (int i = 0, quadsSize = quads.size(); i < quadsSize; i++) { + format.writeQuad(quads.get(i), buffer); + } - buffer.rewind(); + buffer.rewind(); - vbo.unmap(); + vbo.unmap(); + } return new VBOResult(new Vector3i(xi, yi, zi), quads.size() * 4); } finally { @@ -255,8 +258,11 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { GL11.glEnable(GL11.GL_DEPTH_TEST); } - // There aren't any frames in flight, so we can re-use this buffer on the next frame without issue - activeVBO.render(); + //noinspection SynchronizeOnNonFinalField + synchronized (activeVBO) { + // There aren't any frames in flight, so we can re-use this buffer on the next frame without issue + activeVBO.render(); + } GL11.glPopAttrib(); GL11.glPopMatrix(); diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java index 13325d5c..60172ec5 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/StreamingVertexBuffer.java @@ -44,7 +44,7 @@ public StreamingVertexBuffer(VertexFormat format, int drawMode) { /// Generates a new vertex buffer and closes the previous one, if present. /// This should be used sparingly - it's very expensive to call it each frame. - public synchronized void generate() { + public void generate() { if (this.id > 0) { close(); } @@ -53,7 +53,7 @@ public synchronized void generate() { } @Override - public synchronized void close() { + public void close() { if (this.id > 0) { if (mapped) unmap(); @@ -65,17 +65,17 @@ public synchronized void close() { } } - public synchronized void bind() { + public void bind() { if (this.id == 0) throw new IllegalStateException("Cannot bind unallocated VBO"); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.id); } - public synchronized void unbind() { + public void unbind() { GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); } - public synchronized void upload(int usage, ByteBuffer buffer, int vertexCount) { + public void upload(int usage, ByteBuffer buffer, int vertexCount) { if (this.id > 0) { this.vertexCount = vertexCount; this.bind(); @@ -84,11 +84,11 @@ public synchronized void upload(int usage, ByteBuffer buffer, int vertexCount) { } } - public synchronized void upload(ByteBuffer buffer) { + public void upload(ByteBuffer buffer) { this.upload(GL15.GL_STATIC_DRAW, buffer, buffer.remaining() / this.format.getVertexSize()); } - public synchronized void draw(FloatBuffer floatBuffer) { + public void draw(FloatBuffer floatBuffer) { GL11.glPushMatrix(); GL11.glLoadIdentity(); GL11.glMultMatrix(floatBuffer); @@ -96,23 +96,23 @@ public synchronized void draw(FloatBuffer floatBuffer) { GL11.glPopMatrix(); } - public synchronized void draw() { + public void draw() { if (mapped) throw new IllegalStateException("Cannot draw a buffer that is mapped"); GL11.glDrawArrays(this.drawMode, 0, this.vertexCount); } - public synchronized void setupState() { + public void setupState() { this.bind(); this.format.setupBufferState(0L); } - public synchronized void cleanupState() { + public void cleanupState() { this.format.clearBufferState(); this.unbind(); } - public synchronized void render() { + public void render() { this.setupState(); this.draw(); this.cleanupState(); @@ -122,13 +122,13 @@ public synchronized void render() { /// drivers pool memory so there's a good chance it'll just pull it from the pool instead of allocating anything /// since the length is the same. /// Reference: [Buffer Object Streaming](https://wikis.khronos.org/opengl/Buffer_Object_Streaming) - public synchronized void reallocate() { + public void reallocate() { bind(); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, length, bufferFlags); unbind(); } - public synchronized void allocate( + public void allocate( int vertexCount, @MagicConstant(intValues = { GL15.GL_STREAM_DRAW, @@ -155,7 +155,7 @@ public synchronized void allocate( /// Maps the buffer into the client memory space (CPU) and returns a [ByteBuffer] wrapper for it. /// @param access See [glMapBufferRange](https://docs.gl/es3/glMapBufferRange) for more info. - public synchronized ByteBuffer map( + public ByteBuffer map( @MagicConstant(intValues = { GL30.GL_MAP_READ_BIT, GL30.GL_MAP_WRITE_BIT, @@ -187,7 +187,7 @@ public synchronized ByteBuffer map( } @SuppressWarnings("UnusedReturnValue") - public synchronized boolean unmap() { + public boolean unmap() { if (!mapped) throw new IllegalStateException("cannot unmap the same buffer twice"); bind(); From 6ec31070af76608755e1cecabb90d9ece6ba5e9b Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 17 Dec 2025 17:52:18 -0500 Subject: [PATCH 8/8] spotless --- .../common/items/manipulator/RenderHints.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java index 756154ec..1194827f 100644 --- a/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java +++ b/src/main/java/com/recursive_pineapple/matter_manipulator/common/items/manipulator/RenderHints.java @@ -17,7 +17,6 @@ import net.minecraft.util.IIcon; import net.minecraft.world.World; -import net.minecraftforge.client.event.RenderWorldEvent; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.event.world.WorldEvent; @@ -144,7 +143,7 @@ private static VBOResult buildVBO(StreamingVertexBuffer vbo, ArrayList hin final VertexFormat format = DefaultVertexFormat.POSITION_TEXTURE_COLOR; - //noinspection SynchronizationOnLocalVariableOrMethodParameter + // noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (vbo) { vbo.allocate(quads.size() * 4, GL15.GL_STREAM_DRAW); @@ -258,7 +257,7 @@ public static void onRenderWorldLast(RenderWorldLastEvent e) { GL11.glEnable(GL11.GL_DEPTH_TEST); } - //noinspection SynchronizeOnNonFinalField + // noinspection SynchronizeOnNonFinalField synchronized (activeVBO) { // There aren't any frames in flight, so we can re-use this buffer on the next frame without issue activeVBO.render();