Skip to content

Commit 32f231f

Browse files
committed
Fixed buffer corruption issues if the buffer is used over multiple frames
Fixes #298
1 parent 53284d4 commit 32f231f

File tree

2 files changed

+80
-31
lines changed

2 files changed

+80
-31
lines changed

common/src/main/java/net/raphimc/immediatelyfast/feature/core/BatchableBufferSource.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,14 @@ public VertexConsumer getBuffer(final RenderLayer layer) {
7070
final BufferBuilder bufferBuilder;
7171
boolean hasBufferForRenderLayer = layer.areVerticesNotShared() && this.pendingBuffers.containsKey(layer);
7272
if (!layer.areVerticesNotShared()) {
73-
bufferBuilder = new BufferBuilder(BufferAllocatorPool.borrowBufferAllocator(), layer.getDrawMode(), layer.getVertexFormat());
73+
bufferBuilder = new BufferBuilder(this.getNextBufferAllocator(), layer.getDrawMode(), layer.getVertexFormat());
7474
this.currentLayer = layer;
7575
} else if (hasBufferForRenderLayer) {
7676
bufferBuilder = this.pendingBuffers.get(layer).iterator().next();
7777
} else if (this.layerBuffers.containsKey(layer)) {
7878
bufferBuilder = new BufferBuilder(this.layerBuffers.get(layer), layer.getDrawMode(), layer.getVertexFormat());
7979
} else {
80-
bufferBuilder = new BufferBuilder(BufferAllocatorPool.borrowBufferAllocator(), layer.getDrawMode(), layer.getVertexFormat());
80+
bufferBuilder = new BufferBuilder(this.getNextBufferAllocator(), layer.getDrawMode(), layer.getVertexFormat());
8181
this.currentLayer = layer;
8282
}
8383

@@ -168,13 +168,16 @@ public void drawDirect(final RenderLayer layer) {
168168

169169
this.activeLayers.remove(layer);
170170
for (BufferBuilder bufferBuilder : this.getBufferBuilder(layer)) {
171-
final BufferAllocator prevBufferAllocator = bufferBuilder.allocator;
171+
final BufferAllocator prevBufferAllocator = this.allocator;
172172
this.allocator = bufferBuilder.allocator;
173173
this.draw(layer, bufferBuilder);
174174
this.allocator = prevBufferAllocator;
175175
BufferAllocatorPool.returnBufferAllocatorSafe(bufferBuilder.allocator);
176176
}
177177
this.pendingBuffers.remove(layer);
178+
if (this.currentLayer == layer) {
179+
this.currentLayer = null;
180+
}
178181

179182
if (IrisCompat.IRIS_LOADED && !IrisCompat.isRenderingLevel.getAsBoolean()) {
180183
IrisCompat.renderWithExtendedVertexFormat.accept(true);
@@ -225,4 +228,12 @@ protected int getLayerOrder(final RenderLayer layer) {
225228
}
226229
}
227230

231+
private BufferAllocator getNextBufferAllocator() {
232+
if (this.allocator != FALLBACK_BUFFER && this.currentLayer == null && this.allocator.pointer != 0L) {
233+
return this.allocator;
234+
} else {
235+
return BufferAllocatorPool.borrowBufferAllocator();
236+
}
237+
}
238+
228239
}

common/src/main/java/net/raphimc/immediatelyfast/feature/core/BufferAllocatorPool.java

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,46 +18,48 @@
1818
package net.raphimc.immediatelyfast.feature.core;
1919

2020
import com.mojang.blaze3d.systems.RenderSystem;
21-
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
22-
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
21+
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
22+
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
2323
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
2424
import it.unimi.dsi.fastutil.objects.ReferenceList;
2525
import net.minecraft.client.util.BufferAllocator;
2626
import net.raphimc.immediatelyfast.ImmediatelyFast;
2727

2828
public class BufferAllocatorPool {
2929

30-
private static final ReferenceList<BufferAllocator> FREE = new ReferenceArrayList<>();
31-
private static final ReferenceList<BufferAllocator> IN_USE = new ReferenceArrayList<>();
32-
private static final Reference2LongMap<BufferAllocator> BUFFER_ALLOCATOR_ACCESS_TIME = new Reference2LongOpenHashMap<>();
30+
private static final ReferenceList<Entry> FREE = new ReferenceArrayList<>();
31+
private static final ReferenceList<Entry> IN_USE = new ReferenceArrayList<>();
32+
private static final Reference2ObjectMap<BufferAllocator, Entry> BUFFER_ALLOCATOR_MAPPING = new Reference2ObjectOpenHashMap<>();
3333

3434
private BufferAllocatorPool() {
3535
}
3636

3737
public static BufferAllocator borrowBufferAllocator() {
3838
RenderSystem.assertOnRenderThread();
39-
BufferAllocator bufferAllocator;
39+
Entry entry;
4040
if (FREE.isEmpty()) {
41-
bufferAllocator = new BufferAllocator(256);
41+
entry = new Entry(new BufferAllocator(256));
4242
} else {
43-
bufferAllocator = FREE.removeFirst();
44-
if (bufferAllocator.pointer == 0L) { // If the buffer was closed while in the pool
45-
BUFFER_ALLOCATOR_ACCESS_TIME.removeLong(bufferAllocator);
46-
bufferAllocator = new BufferAllocator(256);
43+
entry = FREE.removeFirst();
44+
if (entry.bufferAllocator.pointer == 0L) { // If the buffer was closed while in the pool
45+
BUFFER_ALLOCATOR_MAPPING.remove(entry.bufferAllocator);
46+
entry = new Entry(new BufferAllocator(256));
4747
}
4848
}
49-
IN_USE.add(bufferAllocator);
50-
BUFFER_ALLOCATOR_ACCESS_TIME.put(bufferAllocator, System.currentTimeMillis());
51-
return bufferAllocator;
49+
IN_USE.add(entry);
50+
BUFFER_ALLOCATOR_MAPPING.put(entry.bufferAllocator, entry);
51+
entry.onBorrow();
52+
return entry.bufferAllocator;
5253
}
5354

5455
public static void returnBufferAllocatorSafe(final BufferAllocator bufferAllocator) {
5556
RenderSystem.assertOnRenderThread();
56-
if (!IN_USE.remove(bufferAllocator)) {
57+
final Entry entry = BUFFER_ALLOCATOR_MAPPING.get(bufferAllocator);
58+
if (!IN_USE.remove(entry)) {
5759
return;
5860
}
59-
bufferAllocator.reset();
60-
FREE.add(bufferAllocator);
61+
entry.onReturn();
62+
FREE.addFirst(entry);
6163
}
6264

6365
public static int getSize() {
@@ -66,23 +68,59 @@ public static int getSize() {
6668

6769
public static void onEndFrame() {
6870
if (!IN_USE.isEmpty()) {
69-
ImmediatelyFast.LOGGER.warn(IN_USE.size() + " BufferAllocator(s) were not returned to the pool. Forcibly reclaiming them.");
70-
for (BufferAllocator bufferAllocator : IN_USE) {
71-
bufferAllocator.reset();
71+
// Reclaim all buffers that were not returned to the pool this and the last frame
72+
final boolean leak = IN_USE.removeIf(entry -> {
73+
if (entry.inUseOverMultipleFrames) {
74+
entry.onReturn();
75+
FREE.addFirst(entry);
76+
return true;
77+
}
78+
return false;
79+
});
80+
if (leak) {
81+
ImmediatelyFast.LOGGER.warn("Some BufferAllocators were not returned to the pool. Forcibly reclaiming them to prevent a memory leak.");
82+
}
83+
84+
// Mark all as in use over multiple frames
85+
for (Entry entry : IN_USE) {
86+
entry.inUseOverMultipleFrames = true;
7287
}
73-
FREE.addAll(IN_USE);
74-
IN_USE.clear();
7588
}
76-
BUFFER_ALLOCATOR_ACCESS_TIME.reference2LongEntrySet().removeIf(entry -> {
77-
if (System.currentTimeMillis() - entry.getLongValue() > 60 * 1000) {
78-
if (FREE.contains(entry.getKey())) {
79-
FREE.remove(entry.getKey());
80-
entry.getKey().close();
81-
}
89+
90+
FREE.removeIf(entry -> {
91+
if (entry.shouldBeClosed()) {
92+
entry.bufferAllocator.close();
93+
BUFFER_ALLOCATOR_MAPPING.remove(entry.bufferAllocator);
8294
return true;
8395
}
8496
return false;
8597
});
8698
}
8799

100+
private static class Entry {
101+
102+
private final BufferAllocator bufferAllocator;
103+
private long lastAccessTime;
104+
private boolean inUseOverMultipleFrames;
105+
106+
public Entry(final BufferAllocator bufferAllocator) {
107+
this.bufferAllocator = bufferAllocator;
108+
this.lastAccessTime = System.currentTimeMillis();
109+
}
110+
111+
public boolean shouldBeClosed() {
112+
return System.currentTimeMillis() - this.lastAccessTime > 60 * 1000;
113+
}
114+
115+
public void onBorrow() {
116+
this.lastAccessTime = System.currentTimeMillis();
117+
}
118+
119+
public void onReturn() {
120+
this.bufferAllocator.reset();
121+
this.inUseOverMultipleFrames = false;
122+
}
123+
124+
}
125+
88126
}

0 commit comments

Comments
 (0)