Skip to content

Commit eb4a49d

Browse files
committed
fix: improve voxelizer performance
1 parent 3771a5e commit eb4a49d

File tree

5 files changed

+268
-38
lines changed

5 files changed

+268
-38
lines changed

src/main/java/com/falsepattern/falsetweaks/modules/renderlists/VoxelRenderListManager.java

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
import com.falsepattern.falsetweaks.Share;
2626
import com.falsepattern.falsetweaks.config.RenderListConfig;
2727
import com.falsepattern.falsetweaks.modules.voxelizer.VoxelMesh;
28+
import com.falsepattern.falsetweaks.modules.voxelizer.VoxelMeshIdentity;
29+
import it.unimi.dsi.fastutil.objects.Object2IntMap;
30+
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
31+
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
32+
import it.unimi.dsi.fastutil.objects.ObjectList;
2833
import lombok.AccessLevel;
2934
import lombok.NoArgsConstructor;
3035
import lombok.val;
@@ -34,39 +39,49 @@
3439
import net.minecraft.client.resources.IResourceManager;
3540
import net.minecraft.client.resources.IResourceManagerReloadListener;
3641

37-
import java.util.ArrayList;
38-
import java.util.HashMap;
39-
import java.util.List;
40-
import java.util.Map;
41-
4242
@NoArgsConstructor(access = AccessLevel.PRIVATE)
4343
public class VoxelRenderListManager implements IResourceManagerReloadListener {
4444
public static final VoxelRenderListManager INSTANCE = new VoxelRenderListManager();
4545

46-
private final Map<String, Integer> theMap = new HashMap<>();
47-
private final List<String> identityList = new ArrayList<>();
46+
private final Object2IntMap<VoxelMeshIdentity> theMap = new Object2IntOpenHashMap<>(RenderListConfig.MAX_BUFFER_SIZE, 0.25f);
47+
private final ObjectList<VoxelMeshIdentity> identityList = new ObjectArrayList<>();
4848
private int list = 0;
4949

50+
{
51+
theMap.defaultReturnValue(0);
52+
}
53+
5054
public boolean pre(VoxelMesh mesh, int overlayLayer, boolean remapUV) {
55+
val map = theMap;
5156
val identity = mesh.getIdentity(overlayLayer, remapUV);
52-
if (theMap.containsKey(identity)) {
53-
val list = theMap.get(identity);
54-
identityList.add(identityList.remove(identityList.indexOf(identity)));
55-
GL11.glCallList(list);
57+
val _list = map.getInt(identity);
58+
if (_list != 0) {
59+
lru(identity);
60+
GL11.glCallList(_list);
5661
return true;
5762
} else {
58-
if (identityList.size() >= RenderListConfig.MAX_BUFFER_SIZE) {
59-
val oldIden = identityList.remove(0);
60-
GLAllocation.deleteDisplayLists(theMap.remove(oldIden));
63+
val idList = identityList;
64+
if (idList.size() >= RenderListConfig.MAX_BUFFER_SIZE) {
65+
val oldIden = idList.remove(0);
66+
val deleteId = map.removeInt(oldIden);
67+
if (deleteId != 0) {
68+
GLAllocation.deleteDisplayLists(deleteId);
69+
}
6170
}
62-
list = GLAllocation.generateDisplayLists(1);
63-
identityList.add(identity);
64-
theMap.put(identity, list);
65-
GL11.glNewList(list, GL11.GL_COMPILE);
71+
val newList = GLAllocation.generateDisplayLists(1);
72+
list = newList;
73+
idList.add(identity);
74+
map.put(identity, newList);
75+
GL11.glNewList(newList, GL11.GL_COMPILE);
6676
return false;
6777
}
6878
}
6979

80+
private void lru(VoxelMeshIdentity identity) {
81+
identityList.remove(identity);
82+
identityList.add(identity);
83+
}
84+
7085
public void post() {
7186
GL11.glEndList();
7287
GL11.glCallList(list);

src/main/java/com/falsepattern/falsetweaks/modules/voxelizer/Layer.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@
2525
import com.falsepattern.falsetweaks.modules.voxelizer.interfaces.ITextureAtlasSpriteMixin;
2626
import com.falsepattern.lib.util.MathUtil;
2727
import lombok.RequiredArgsConstructor;
28+
import lombok.val;
29+
import lombok.var;
30+
import org.jetbrains.annotations.NotNull;
2831

2932
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
3033

34+
import java.lang.ref.SoftReference;
35+
3136
@RequiredArgsConstructor
3237
public class Layer {
3338
public final TextureAtlasSprite texture;
3439
public final float thickness;
3540

41+
private SoftReference<LayerIdentity> cachedIdentity = null;
42+
3643
private ITextureAtlasSpriteMixin tex() {
3744
return (ITextureAtlasSpriteMixin) texture;
3845
}
@@ -72,7 +79,37 @@ private float fetch(float a, float A, float min, float max) {
7279
return (float) MathUtil.clampedLerp(min, max, a / A);
7380
}
7481

75-
public String textureIdentity() {
76-
return texture.getIconName() + '|' + tex().frameCounter();
82+
public @NotNull LayerIdentity textureIdentity() {
83+
val cached = cachedIdentity;
84+
if (cached == null) {
85+
return createNewIdentity();
86+
}
87+
val id = cached.get();
88+
if (id == null) {
89+
return createNewIdentity();
90+
}
91+
var name = texture.getIconName();
92+
if (name == null) {
93+
name = "";
94+
}
95+
val counter = tex().frameCounter();
96+
if (!id.equals(name, counter)) {
97+
return createNewIdentity(name, counter);
98+
}
99+
return id;
100+
}
101+
102+
private @NotNull LayerIdentity createNewIdentity() {
103+
var n = texture.getIconName();
104+
if (n == null) {
105+
n = "";
106+
}
107+
return createNewIdentity(n, tex().frameCounter());
108+
}
109+
110+
private @NotNull LayerIdentity createNewIdentity(@NotNull String name, int frameCounter) {
111+
val id = new LayerIdentity(name, frameCounter);
112+
cachedIdentity = new SoftReference<>(id);
113+
return id;
77114
}
78115
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* This file is part of FalseTweaks.
3+
*
4+
* Copyright (C) 2022-2025 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalseTweaks is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, only version 3 of the License.
13+
*
14+
* FalseTweaks is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public License
20+
* along with FalseTweaks. If not, see <https://www.gnu.org/licenses/>.
21+
*/
22+
23+
package com.falsepattern.falsetweaks.modules.voxelizer;
24+
25+
import lombok.RequiredArgsConstructor;
26+
import lombok.val;
27+
import org.jetbrains.annotations.NotNull;
28+
29+
@RequiredArgsConstructor
30+
public final class LayerIdentity {
31+
private final @NotNull String iconName;
32+
private final int frameCounter;
33+
34+
@Override
35+
public boolean equals(Object obj) {
36+
if (!(obj instanceof LayerIdentity))
37+
return false;
38+
val o = (LayerIdentity) obj;
39+
return equals(o.iconName, o.frameCounter);
40+
}
41+
42+
public boolean equals(@NotNull String iconName, int frameCounter) {
43+
return frameCounter == this.frameCounter && iconName.equals(this.iconName);
44+
}
45+
46+
@Override
47+
public int hashCode() {
48+
int result = 1;
49+
result = result * 31 + iconName.hashCode();
50+
result = result * 31 + Integer.hashCode(frameCounter);
51+
return result;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return iconName + "|" + frameCounter;
57+
}
58+
}

src/main/java/com/falsepattern/falsetweaks/modules/voxelizer/VoxelMesh.java

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@
2828
import com.falsepattern.falsetweaks.modules.voxelizer.strategy.MergingStrategy;
2929
import com.falsepattern.lib.util.MathUtil;
3030
import lombok.val;
31+
import lombok.var;
32+
import org.jetbrains.annotations.NotNull;
3133
import org.joml.Matrix4f;
3234
import org.joml.Matrix4fc;
3335
import org.joml.Vector3f;
3436

3537
import net.minecraft.client.renderer.Tessellator;
3638
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
3739

40+
import java.lang.ref.SoftReference;
3841
import java.util.List;
3942
import java.util.Map;
4043
import java.util.Objects;
@@ -52,7 +55,8 @@ public class VoxelMesh {
5255
private final float[] zOffsets;
5356
private final VoxelCompiler compiler;
5457
private Map<VoxelType, List<Face>> faceCache;
55-
private String cacheIdentity = null;
58+
private VoxelMeshIdentity compiledIdentity = null;
59+
private SoftReference<VoxelMeshIdentity> cachedIdentity = null;
5660

5761
public VoxelMesh(MergingStrategy strategy, Layer... layers) {
5862
this.strategy = strategy;
@@ -217,34 +221,60 @@ public void renderToTessellator(Tessellator tess,
217221
}
218222

219223
public void compile() {
220-
String currentIdentity = getIdentity(0, false);
221-
if (!Objects.equals(cacheIdentity, currentIdentity)) {
224+
val currentIdentity = getIdentity(0, false);
225+
if (!Objects.equals(compiledIdentity, currentIdentity)) {
222226
if (VoxelizerConfig.DEBUG_MESH_COMPILATION) {
223-
Share.log.info("Starting compilation for mesh \"" + currentIdentity + "\"");
227+
Share.log.info("Starting compilation for mesh \"{}\"", currentIdentity);
224228
}
225229
faceCache = compiler.compile(strategy);
226230
if (VoxelizerConfig.DEBUG_MESH_COMPILATION) {
227-
Share.log.info("Compiled mesh \"" + currentIdentity + "\" with " + faceCache.size() + " faces!");
231+
var size = 0;
232+
for (val entry: faceCache.entrySet()) {
233+
size += entry.getValue().size();
234+
}
235+
Share.log.info("Compiled mesh \"{}\" with {} faces!", currentIdentity, size);
228236
}
229-
cacheIdentity = currentIdentity;
237+
compiledIdentity = currentIdentity;
230238
}
231239
}
232240

233-
public String getIdentity(int overlayLayer, boolean remapUV) {
234-
val result = new StringBuilder();
235-
if (remapUV) {
236-
result.append("remap_uv!");
241+
public VoxelMeshIdentity getIdentity(int overlayLayer, boolean remapUV) {
242+
val cached = cachedIdentity;
243+
if (cached == null) {
244+
return createNewIdentity(overlayLayer, remapUV);
245+
}
246+
val id = cached.get();
247+
if (id == null) {
248+
return createNewIdentity(overlayLayer, remapUV);
249+
}
250+
if (!id.partialEquals(overlayLayer, remapUV)) {
251+
return createNewIdentity(overlayLayer, remapUV);
237252
}
238-
if (overlayLayer > 0) {
239-
result.append("overlay")
240-
.append(overlayLayer)
241-
.append("!");
253+
val layers = getLayerIdentities();
254+
if (!id.partialEquals(layers)) {
255+
return createNewIdentity(overlayLayer, remapUV, layers);
242256
}
243-
for (val layer : layers) {
244-
result.append(layer.textureIdentity())
245-
.append('&');
257+
return id;
258+
}
259+
260+
private @NotNull VoxelMeshIdentity createNewIdentity(int overlayLayer, boolean remapUV) {
261+
return createNewIdentity(overlayLayer, remapUV, getLayerIdentities());
262+
}
263+
264+
private @NotNull VoxelMeshIdentity createNewIdentity(int overlayLayer, boolean remapUv, @NotNull LayerIdentity @NotNull[] layers) {
265+
val id = new VoxelMeshIdentity(remapUv, overlayLayer, layers);
266+
cachedIdentity = new SoftReference<>(id);
267+
return id;
268+
}
269+
270+
private @NotNull LayerIdentity @NotNull[] getLayerIdentities() {
271+
val layers = this.layers;
272+
val layerCount = layers.length;
273+
val layerIdentities = new LayerIdentity[layerCount];
274+
for (int i = 0; i < layerCount; i++) {
275+
layerIdentities[i] = layers[i].textureIdentity();
246276
}
247-
return result.toString();
277+
return layerIdentities;
248278
}
249279

250280
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* This file is part of FalseTweaks.
3+
*
4+
* Copyright (C) 2022-2025 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalseTweaks is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, only version 3 of the License.
13+
*
14+
* FalseTweaks is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public License
20+
* along with FalseTweaks. If not, see <https://www.gnu.org/licenses/>.
21+
*/
22+
23+
package com.falsepattern.falsetweaks.modules.voxelizer;
24+
25+
import lombok.RequiredArgsConstructor;
26+
import lombok.val;
27+
import org.jetbrains.annotations.NotNull;
28+
29+
import java.util.Arrays;
30+
31+
@RequiredArgsConstructor
32+
public final class VoxelMeshIdentity {
33+
private static final long HC_MARKER = 0xFFFFFFFF00000000L;
34+
private final boolean remapUv;
35+
private final int overlayLayer;
36+
private final @NotNull LayerIdentity @NotNull[] layers;
37+
private long __hashcode = HC_MARKER;
38+
39+
@Override
40+
public boolean equals(Object obj) {
41+
if (!(obj instanceof VoxelMeshIdentity))
42+
return false;
43+
val o = (VoxelMeshIdentity) obj;
44+
return equals(o.remapUv, o.overlayLayer, o.layers);
45+
}
46+
47+
public boolean partialEquals(int overlayLayer, boolean remapUv) {
48+
return remapUv == this.remapUv && overlayLayer == this.overlayLayer;
49+
}
50+
51+
public boolean partialEquals(@NotNull LayerIdentity @NotNull[] layers) {
52+
return Arrays.equals(layers, this.layers);
53+
}
54+
55+
public boolean equals(boolean remapUv, int overlayLayer, @NotNull LayerIdentity @NotNull[] layers) {
56+
return remapUv == this.remapUv && overlayLayer == this.overlayLayer && Arrays.equals(layers, this.layers);
57+
}
58+
59+
@Override
60+
public int hashCode() {
61+
val hc = __hashcode;
62+
if (hc != HC_MARKER) {
63+
return (int)(hc & 0xFFFFFFFFL);
64+
}
65+
int result = 1;
66+
result = 31 * result + Boolean.hashCode(remapUv);
67+
result = 31 * result + Integer.hashCode(overlayLayer);
68+
result = 31 * result + Arrays.hashCode(layers);
69+
__hashcode = result & 0xFFFFFFFFL;
70+
return result;
71+
}
72+
73+
@Override
74+
public String toString() {
75+
val result = new StringBuilder();
76+
if (remapUv) {
77+
result.append("remap_uv!");
78+
}
79+
if (overlayLayer > 0) {
80+
result.append("overlay")
81+
.append(overlayLayer)
82+
.append("!");
83+
}
84+
for (val layer : layers) {
85+
result.append(layer)
86+
.append('&');
87+
}
88+
return result.toString();
89+
}
90+
}

0 commit comments

Comments
 (0)