From 1d5459013bae7eef9bc583d075bba6728173fb73 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Tue, 10 Mar 2026 02:04:32 +0800 Subject: [PATCH 1/2] Optimize bind cache and primitive hot paths --- .../com/xtracr/realcamera/RealCameraCore.java | 21 +- .../xtracr/realcamera/config/BindTarget.java | 58 ++++-- .../xtracr/realcamera/config/ModConfig.java | 2 +- .../realcamera/util/BuiltIterableBuffer.java | 186 ++++++++++++------ .../realcamera/util/IterableVertexBuffer.java | 143 +++++++++++++- 5 files changed, 330 insertions(+), 80 deletions(-) diff --git a/common/src/main/java/com/xtracr/realcamera/RealCameraCore.java b/common/src/main/java/com/xtracr/realcamera/RealCameraCore.java index a860ecf..dcba273 100644 --- a/common/src/main/java/com/xtracr/realcamera/RealCameraCore.java +++ b/common/src/main/java/com/xtracr/realcamera/RealCameraCore.java @@ -22,6 +22,7 @@ public class RealCameraCore { private static Vec3 cameraPos = Vec3.ZERO, eulerAngle = Vec3.ZERO; private static boolean active = false, rendering = false; private static int failureFrames = 0; + private static final java.util.Map> DISABLE_CONFIG_CACHE = new java.util.HashMap<>(); public static boolean isActive() { return active; @@ -108,6 +109,11 @@ public static void computeCamera(Minecraft client, float deltaTick) { eulerAngle = MathUtil.getEulerAngleYXZ(SmoothUtil.smoothRotation(lastResult.getRotation())).scale(Math.toDegrees(1)); } + private static DisableConfig[] disableConfigsFor(BindTarget target, String textureId) { + return DISABLE_CONFIG_CACHE.computeIfAbsent(target, unused -> new java.util.HashMap<>()) + .computeIfAbsent(textureId, key -> target.filteredDisableConfigs(config -> key.contains(config.textureId()))); + } + public static void renderCameraEntity(Minecraft client, float deltaTick, MultiBufferSource bufferSource, Matrix4f modelView) { Vec3 targetEulerAngle = MathUtil.getEulerAngleYXZ(lastResult.getRotation()); Matrix4f invertedCameraPose = new Matrix4f() @@ -126,7 +132,7 @@ public static void renderCameraEntity(Minecraft client, float deltaTick, MultiBu final float m02 = modelView.m02(), m12 = modelView.m12(), m22 = modelView.m22(), m32 = modelView.m32(); final float depth = currentTarget().disablingDepth(); catcher.endCatching(builtBuffer -> { - DisableConfig[] disableConfigs = currentTarget().filteredDisableConfigs(config -> builtBuffer.textureId().contains(config.textureId())); + DisableConfig[] disableConfigs = disableConfigsFor(currentTarget(), builtBuffer.textureId()); for (DisableConfig config : disableConfigs) { if (config.disableAll()) return; } @@ -135,7 +141,7 @@ public static void renderCameraEntity(Minecraft client, float deltaTick, MultiBu for (VertexData vertex : builtBuffer.vertexBuffer()) vertex.render(buffer); return; } - builtBuffer.vertexBuffer().primitiveStream().forEach(primitive -> { + builtBuffer.vertexBuffer().forEachPrimitive(primitive -> { primitiveFor: for (VertexData vertex : primitive) { if (Math.fma(m02, vertex.x(), Math.fma(m12, vertex.y(), Math.fma(m22, vertex.z(), m32))) > -depth) continue; @@ -156,16 +162,13 @@ private static void computeBindResult(BuiltIterableBuffer builtBuffer) { BindResult result = new BindResult(target, false); BindTarget.TargetConfig config = target.targetConfig(); VertexData.UV[] uvs = {new VertexData.UV(config.posU(), config.posV()), new VertexData.UV(config.forwardU(), config.forwardV()), new VertexData.UV(config.upwardU(), config.upwardV())}; - VertexData[][] primitives = builtBuffer.findPrimitivesInCache(uvs); - if (builtBuffer.anyNotCached(uvs)) { + VertexData[][] primitives = new VertexData[uvs.length][]; + int unresolved = builtBuffer.findPrimitivesInCache(uvs, primitives); + if (unresolved != 0) { for (int i = 0; i < primitives.length; i++) { if (primitives[i] != null) uvs[i] = null; } - VertexData[][] newPrimitives = builtBuffer.findPrimitives(uvs); - for (int i = 0; i < primitives.length; i++) { - if (newPrimitives[i] == null && primitives[i] == null) continue targetFor; - else if (newPrimitives[i] != null) primitives[i] = newPrimitives[i]; - } + if (builtBuffer.findPrimitives(uvs, primitives, unresolved) != 0) continue; } if (primitives[0] != null) result.setPosition(VertexData.position(primitives[0], config.posU(), config.posV())); if (primitives[1] != null) result.setForward(VertexData.normal(primitives[1])); diff --git a/common/src/main/java/com/xtracr/realcamera/config/BindTarget.java b/common/src/main/java/com/xtracr/realcamera/config/BindTarget.java index 290bae3..dca64b8 100644 --- a/common/src/main/java/com/xtracr/realcamera/config/BindTarget.java +++ b/common/src/main/java/com/xtracr/realcamera/config/BindTarget.java @@ -5,8 +5,7 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.xtracr.realcamera.util.VertexData; -import it.unimi.dsi.fastutil.floats.Float2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; +import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.util.Mth; @@ -74,7 +73,18 @@ public boolean isEmpty() { } public DisableConfig[] filteredDisableConfigs(Predicate filter) { - return Arrays.stream(disableConfigs).filter(filter).toArray(DisableConfig[]::new); + int count = 0; + for (DisableConfig disableConfig : disableConfigs) { + if (filter.test(disableConfig)) count++; + } + if (count == 0) return new DisableConfig[0]; + if (count == disableConfigs.length) return disableConfigs; + DisableConfig[] filtered = new DisableConfig[count]; + for (int i = 0, j = 0; i < disableConfigs.length; i++) { + DisableConfig disableConfig = disableConfigs[i]; + if (filter.test(disableConfig)) filtered[j++] = disableConfig; + } + return filtered; } public void write(FriendlyByteBuf byteBuf) { @@ -210,18 +220,23 @@ public void write(FriendlyByteBuf byteBuf) { @JsonAdapter(DisableConfig.Adapter.class) public static class DisableConfig { - private final Float2ObjectOpenHashMap disableCacheMap = new Float2ObjectOpenHashMap<>(); + private static final byte UNKNOWN = 0, DISABLED = 1, ENABLED = 2; + private static final int RECENT_CACHE_MASK = 15; + private final Long2ByteOpenHashMap disableCacheMap; private final String name; private final String textureId; private final boolean disableAll; private final UVRectangle[] rectangles; + private final long[] recentUvKeys = new long[RECENT_CACHE_MASK + 1]; + private final byte[] recentStates = new byte[RECENT_CACHE_MASK + 1]; public DisableConfig(String name, String textureId, boolean disableAll, UVRectangle[] rectangles) { this.name = name; this.textureId = textureId; this.disableAll = disableAll; this.rectangles = rectangles; - disableCacheMap.defaultReturnValue(FloatOpenHashSet.of()); + disableCacheMap = new Long2ByteOpenHashMap(Math.max(256, rectangles.length * 8), 0.5f); + disableCacheMap.defaultReturnValue(UNKNOWN); } public static DisableConfig read(FriendlyByteBuf byteBuf) { @@ -263,20 +278,41 @@ public void write(FriendlyByteBuf byteBuf) { public boolean disable(VertexData vertex) { final float u = vertex.u(), v = vertex.v(); - final FloatOpenHashSet cachedVs = disableCacheMap.get(u); - if (!cachedVs.isEmpty()) { - if (cachedVs.contains(v)) return true; - if (cachedVs.contains(-v)) return false; + final long uv = uvKey(u, v); + final int slot = recentSlot(uv); + final byte recentState = recentStates[slot]; + if (recentState != UNKNOWN && recentUvKeys[slot] == uv) return recentState == DISABLED; + final byte cached = disableCacheMap.get(uv); + if (cached != UNKNOWN) { + rememberRecent(uv, cached); + return cached == DISABLED; } for (UVRectangle rect : rectangles) { if (!rect.contains(u, v)) continue; - disableCacheMap.computeIfAbsent(u, k -> new FloatOpenHashSet()).add(v); + disableCacheMap.put(uv, DISABLED); + rememberRecent(uv, DISABLED); return true; } - disableCacheMap.computeIfAbsent(u, k -> new FloatOpenHashSet()).add(-v); + disableCacheMap.put(uv, ENABLED); + rememberRecent(uv, ENABLED); return false; } + private static long uvKey(float u, float v) { + return (long) Float.floatToRawIntBits(u) << 32 | (Float.floatToRawIntBits(v) & 0xffffffffL); + } + + private static int recentSlot(long uv) { + long mixed = uv ^ (uv >>> 33) ^ (uv >>> 17); + return (int) mixed & RECENT_CACHE_MASK; + } + + private void rememberRecent(long uv, byte state) { + int slot = recentSlot(uv); + recentUvKeys[slot] = uv; + recentStates[slot] = state; + } + public static class Adapter extends TypeAdapter { @Override public void write(JsonWriter out, DisableConfig value) throws IOException { diff --git a/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java b/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java index da03938..45b2c85 100644 --- a/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java +++ b/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java @@ -330,4 +330,4 @@ private void clamp() { } } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/xtracr/realcamera/util/BuiltIterableBuffer.java b/common/src/main/java/com/xtracr/realcamera/util/BuiltIterableBuffer.java index 5f5e7f2..099ead4 100644 --- a/common/src/main/java/com/xtracr/realcamera/util/BuiltIterableBuffer.java +++ b/common/src/main/java/com/xtracr/realcamera/util/BuiltIterableBuffer.java @@ -1,12 +1,10 @@ package com.xtracr.realcamera.util; - import com.mojang.blaze3d.vertex.MeshData; import com.xtracr.realcamera.util.VertexData.UV; import net.minecraft.client.renderer.RenderType; import org.jetbrains.annotations.Nullable; -import java.awt.*; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -15,7 +13,9 @@ public record BuiltIterableBuffer(RenderType renderType, String textureId, IterableVertexBuffer vertexBuffer) { private static final Pattern TEXTURE_ID_PATTERN = Pattern.compile("texture\\[Optional\\[(.*?)]"); private static final Map TEXTURE_ID_CACHE = new HashMap<>(); - private static final Map> FIND_PRIMITIVE_CACHE = new HashMap<>(); + private static final Map> FIND_PRIMITIVE_CACHE = new HashMap<>(); + + private record PrimitiveCache(int index, float[] uvCache) { } public static BuiltIterableBuffer buildFrom(RenderType renderType, MeshData meshData) { String textureId = TEXTURE_ID_CACHE.computeIfAbsent(renderType, rt -> { @@ -27,77 +27,151 @@ public static BuiltIterableBuffer buildFrom(RenderType renderType, MeshData mesh } public boolean anyNotCached(UV[] uvs) { - Map cache = FIND_PRIMITIVE_CACHE.get(renderType); + Map cache = FIND_PRIMITIVE_CACHE.get(renderType); if (cache == null) return true; for (UV uv : uvs) { - if (!cache.containsKey(uv)) return true; + if (uv != null && !cache.containsKey(uv)) return true; } return false; } public VertexData[] @Nullable [] findPrimitivesInCache(UV[] uvs) { - Map cache = FIND_PRIMITIVE_CACHE.get(renderType); - int length = renderType.mode().primitiveLength, uvsLength = uvs.length; - VertexData[][] primitives = new VertexData[uvsLength][]; - if (cache == null) return primitives; - float[][] uvCacheArray = new float[uvsLength][]; - boolean allNull = true; - for (int i = 0; i < uvsLength; i++) { - uvCacheArray[i] = cache.get(uvs[i]); - if (uvCacheArray[i] != null) allNull = false; + VertexData[][] primitives = new VertexData[uvs.length][]; + findPrimitivesInCache(uvs, primitives); + return primitives; + } + + public int findPrimitivesInCache(UV[] uvs, VertexData[] @Nullable [] primitives) { + Map cache = FIND_PRIMITIVE_CACHE.get(renderType); + int unresolved = 0; + if (cache == null) { + for (UV uv : uvs) { + if (uv != null) unresolved++; + } + return unresolved; } - if (allNull) return primitives; - vertexBuffer.primitiveStream().anyMatch(primitive -> { - float[] uvCache; - boolean allFound = true; - cacheFor: - for (int i = 0; i < uvsLength; i++) { - if (primitives[i] != null) continue; - uvCache = uvCacheArray[i]; - if (uvCache == null) continue; - for (int j = 0; j < length; j++) { - if (uvCache[j * 2] != primitive[j].u() || uvCache[j * 2 + 1] != primitive[j].v()) { - allFound = false; - continue cacheFor; - } + int primitiveCount = vertexBuffer.primitiveCount(); + int cachedIndex0 = -1, cachedIndex1 = -1, cachedIndex2 = -1; + VertexData[] primitive0 = null, primitive1 = null, primitive2 = null; + for (int i = 0; i < uvs.length; i++) { + UV uv = uvs[i]; + if (uv == null) continue; + PrimitiveCache cachedPrimitive = cache.get(uv); + if (cachedPrimitive == null) { + unresolved++; + continue; + } + int primitiveIndex = cachedPrimitive.index(); + if (primitiveIndex >= primitiveCount) { + cache.remove(uv); + unresolved++; + continue; + } + if (!vertexBuffer.primitiveMatchesUVs(primitiveIndex, cachedPrimitive.uvCache())) { + cache.remove(uv); + unresolved++; + continue; + } + VertexData[] primitive; + if (primitiveIndex == cachedIndex0) primitive = primitive0; + else if (primitiveIndex == cachedIndex1) primitive = primitive1; + else if (primitiveIndex == cachedIndex2) primitive = primitive2; + else { + primitive = vertexBuffer.readPrimitiveAt(primitiveIndex); + if (cachedIndex0 == -1) { + cachedIndex0 = primitiveIndex; + primitive0 = primitive; + } else if (cachedIndex1 == -1) { + cachedIndex1 = primitiveIndex; + primitive1 = primitive; + } else { + cachedIndex2 = primitiveIndex; + primitive2 = primitive; } - primitives[i] = VertexData.asImmutable(primitive); } - return allFound; - }); - return primitives; + primitives[i] = primitive; + } + return unresolved; } public VertexData[] @Nullable [] findPrimitives(UV[] uvs) { - final int resolution = 1000000; - int length = renderType.mode().primitiveLength, uvsLength = uvs.length; - int[] us = new int[length], vs = new int[length]; - VertexData[][] primitives = new VertexData[uvsLength][]; - vertexBuffer.primitiveStream().anyMatch(primitive -> { - for (int i = 0; i < length; i++) { - us[i] = (int) (resolution * primitive[i].u()); - vs[i] = (int) (resolution * primitive[i].v()); + UV[] unresolvedUvs = uvs.clone(); + VertexData[][] primitives = new VertexData[unresolvedUvs.length][]; + int unresolved = 0; + for (UV uv : unresolvedUvs) { + if (uv != null) unresolved++; + } + findPrimitives(unresolvedUvs, primitives, unresolved); + return primitives; + } + + public int findPrimitives(UV[] uvs, VertexData[] @Nullable [] primitives, int unresolved) { + int length = renderType.mode().primitiveLength; + float[] primitiveUvs = new float[length * 2]; + Map cache = FIND_PRIMITIVE_CACHE.computeIfAbsent(renderType, k -> new HashMap<>()); + for (int primitiveIndex = 0, primitiveCount = vertexBuffer.primitiveCount(); primitiveIndex < primitiveCount && unresolved > 0; primitiveIndex++) { + vertexBuffer.readPrimitiveUVs(primitiveIndex, primitiveUvs); + float minU = primitiveUvs[0], maxU = primitiveUvs[0], minV = primitiveUvs[1], maxV = primitiveUvs[1]; + for (int i = 1; i < length; i++) { + float primitiveU = primitiveUvs[i * 2], primitiveV = primitiveUvs[i * 2 + 1]; + minU = Math.min(minU, primitiveU); + maxU = Math.max(maxU, primitiveU); + minV = Math.min(minV, primitiveV); + maxV = Math.max(maxV, primitiveV); } - Polygon polygon = new Polygon(us, vs, length); - boolean allFound = true; - for (int i = 0; i < uvsLength; i++) { - if (primitives[i] != null) continue; + VertexData[] primitive = null; + float[] uvCache = null; + for (int i = 0; i < uvs.length; i++) { UV uv = uvs[i]; if (uv == null) continue; - if (!polygon.contains(resolution * uv.u(), resolution * uv.v())) { - allFound = false; + double u = uv.u(), v = uv.v(); + if (u < minU || u > maxU || v < minV || v > maxV || !primitiveContains(primitiveUvs, length, u, v)) { continue; } - float[] uvCache = new float[length * 2]; - for (int j = 0; j < length; j++) { - uvCache[j * 2] = primitive[j].u(); - uvCache[j * 2 + 1] = primitive[j].v(); + if (primitive == null) { + primitive = vertexBuffer.readPrimitiveAt(primitiveIndex); + } + if (uvCache == null) { + uvCache = primitiveUvs.clone(); } - FIND_PRIMITIVE_CACHE.computeIfAbsent(renderType, k -> new HashMap<>()).put(uv, uvCache); - primitives[i] = VertexData.asImmutable(primitive); + cache.put(uv, new PrimitiveCache(primitiveIndex, uvCache)); + primitives[i] = primitive; + uvs[i] = null; + unresolved--; } - return allFound; - }); - return primitives; + } + return unresolved; + } + + private static boolean primitiveContains(float[] primitiveUvs, int length, double u, double v) { + if (length == 0) return false; + if (length == 1) { + return primitiveUvs[0] == u && primitiveUvs[1] == v; + } + if (length == 2) { + return pointOnSegment(primitiveUvs[0], primitiveUvs[1], primitiveUvs[2], primitiveUvs[3], u, v); + } + boolean inside = false; + for (int i = 0, j = length - 1; i < length; j = i++) { + int current = i * 2; + int previous = j * 2; + double currentU = primitiveUvs[current], currentV = primitiveUvs[current + 1]; + double previousU = primitiveUvs[previous], previousV = primitiveUvs[previous + 1]; + if (pointOnSegment(currentU, currentV, previousU, previousV, u, v)) { + return true; + } + boolean intersects = ((currentV > v) != (previousV > v)) && + (u < (previousU - currentU) * (v - currentV) / (previousV - currentV) + currentU); + if (intersects) { + inside = !inside; + } + } + return inside; + } + + private static boolean pointOnSegment(double u1, double v1, double u2, double v2, double u, double v) { + double cross = (u2 - u1) * (v - v1) - (v2 - v1) * (u - u1); + if (Math.abs(cross) > 1.0E-7D) return false; + return u >= Math.min(u1, u2) && u <= Math.max(u1, u2) && v >= Math.min(v1, v2) && v <= Math.max(v1, v2); } -} +} \ No newline at end of file diff --git a/common/src/main/java/com/xtracr/realcamera/util/IterableVertexBuffer.java b/common/src/main/java/com/xtracr/realcamera/util/IterableVertexBuffer.java index 8c67957..2f202ec 100644 --- a/common/src/main/java/com/xtracr/realcamera/util/IterableVertexBuffer.java +++ b/common/src/main/java/com/xtracr/realcamera/util/IterableVertexBuffer.java @@ -21,11 +21,12 @@ public class IterableVertexBuffer implements Iterable { private static final boolean IS_LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN; public final int vertexCount, vertexSize; + private final int primitiveLength, primitiveStride, primitiveCount; private final MutableVertex reusableVertex = VertexData.mutable(); private final Iterable primitives; private final ByteBuffer buffer; private final int positionOffset, colorOffset, uvOffset, overlayOffset, lightOffset, normalOffset; - private final boolean hasPosition, hasColor, hasUV, hasOverlay, hasLight, hasNormal, fastFormat; + private final boolean hasPosition, hasColor, hasUV, hasOverlay, hasLight, hasNormal, fastFormat, startWithFirst; public IterableVertexBuffer(MeshData meshData) { buffer = meshData.vertexBuffer(); @@ -46,8 +47,13 @@ public IterableVertexBuffer(MeshData meshData) { hasLight = lightOffset != -1; hasNormal = normalOffset != -1; fastFormat = format == DefaultVertexFormat.NEW_ENTITY; - boolean isQuad = drawState.mode() == VertexFormat.Mode.QUADS; - primitives = fastFormat && isQuad ? new FastQuadReader() : new PrimitiveReader(drawState.mode()); + VertexFormat.Mode drawMode = drawState.mode(); + primitiveLength = drawMode.primitiveLength; + primitiveStride = drawMode.primitiveStride; + primitiveCount = vertexCount < primitiveLength ? 0 : (vertexCount - primitiveLength) / primitiveStride + 1; + startWithFirst = drawMode == VertexFormat.Mode.TRIANGLE_FAN; + boolean isQuad = drawMode == VertexFormat.Mode.QUADS; + primitives = fastFormat && isQuad ? new FastQuadReader() : new PrimitiveReader(drawMode); } public Iterable primitives() { @@ -62,6 +68,36 @@ public Stream primitiveStream() { return StreamSupport.stream(primitives.spliterator(), false); } + public void forEachPrimitive(Consumer action) { + if (primitives instanceof FastQuadReader fastQuadReader) { + fastQuadReader.forEachPrimitive(action); + return; + } + ((PrimitiveReader) primitives).forEachPrimitive(action); + } + + public boolean isFastQuadFormat() { + return primitives instanceof FastQuadReader; + } + + public MutableVertex[] newMutablePrimitive() { + MutableVertex[] primitive = new MutableVertex[primitiveLength]; + for (int i = 0; i < primitiveLength; i++) { + primitive[i] = VertexData.mutable(); + } + return primitive; + } + + public void readFastQuadPositionUVAt(int index, MutableVertex[] quad) { + if (!(primitives instanceof FastQuadReader fastQuadReader)) throw new IllegalStateException("Fast quad format required"); + fastQuadReader.fastReadQuadPositionUVAt(index, quad); + } + + public void readFastQuadAt(int index, MutableVertex[] quad) { + if (!(primitives instanceof FastQuadReader fastQuadReader)) throw new IllegalStateException("Fast quad format required"); + fastQuadReader.fastReadQuadAt(index, quad); + } + public VertexData readVertexAt(int index) { return readVertexAt(index, reusableVertex); } @@ -112,6 +148,69 @@ public MutableVertex readVertexAt(int index, MutableVertex mutable) { return mutable; } + public int primitiveCount() { + return primitiveCount; + } + + public void readPrimitiveUVs(int index, float[] uvs) { + Objects.checkIndex(index, primitiveCount); + Objects.checkFromIndexSize(0, primitiveLength * 2, uvs.length); + int vertexIndex = index * primitiveStride; + if (startWithFirst) { + readVertexUVAt(0, uvs, 0); + } + for (int i = startWithFirst ? 1 : 0; i < primitiveLength; i++) { + readVertexUVAt(vertexIndex + i, uvs, i * 2); + } + } + + public boolean primitiveMatchesUVs(int index, float[] uvs) { + Objects.checkIndex(index, primitiveCount); + Objects.checkFromIndexSize(0, primitiveLength * 2, uvs.length); + int vertexIndex = index * primitiveStride; + if (startWithFirst && !vertexMatchesUVAt(0, uvs, 0)) { + return false; + } + for (int i = startWithFirst ? 1 : 0; i < primitiveLength; i++) { + if (!vertexMatchesUVAt(vertexIndex + i, uvs, i * 2)) { + return false; + } + } + return true; + } + + public VertexData[] readPrimitiveAt(int index) { + Objects.checkIndex(index, primitiveCount); + VertexData[] primitive = new VertexData[primitiveLength]; + int vertexIndex = index * primitiveStride; + if (startWithFirst) { + primitive[0] = readVertexAt(0).asImmutable(); + } + for (int i = startWithFirst ? 1 : 0; i < primitiveLength; i++) { + primitive[i] = readVertexAt(vertexIndex + i).asImmutable(); + } + return primitive; + } + + private void readVertexUVAt(int index, float[] uvs, int uvIndex) { + if (!hasUV) { + uvs[uvIndex] = 0; + uvs[uvIndex + 1] = 0; + return; + } + int vertexOffset = index * vertexSize + uvOffset; + uvs[uvIndex] = buffer.getFloat(vertexOffset); + uvs[uvIndex + 1] = buffer.getFloat(vertexOffset + 4); + } + + private boolean vertexMatchesUVAt(int index, float[] uvs, int uvIndex) { + if (!hasUV) { + return uvs[uvIndex] == 0 && uvs[uvIndex + 1] == 0; + } + int vertexOffset = index * vertexSize + uvOffset; + return buffer.getFloat(vertexOffset) == uvs[uvIndex] && buffer.getFloat(vertexOffset + 4) == uvs[uvIndex + 1]; + } + @Override public @NotNull Iterator iterator() { return new VertexIterator(); @@ -302,6 +401,20 @@ public PrimitiveReader(VertexFormat.Mode drawMode) { startWithFirst = drawMode == VertexFormat.Mode.TRIANGLE_FAN; } + public void forEachPrimitive(Consumer action) { + MutableVertex[] reusablePrimitive = new MutableVertex[primitiveLength]; + for (int i = 0; i < primitiveLength; i++) { + reusablePrimitive[i] = VertexData.mutable(); + } + if (startWithFirst && 0 < vertexCount) { + readVertexAt(0, reusablePrimitive[0]); + } + for (int primitiveIndex = 0; primitiveIndex < primitiveCount; primitiveIndex++) { + readPrimitiveAt(primitiveIndex, reusablePrimitive); + action.accept(reusablePrimitive); + } + } + @Override public @NotNull Iterator iterator() { return new PrimitiveIterator(); @@ -396,6 +509,17 @@ public int characteristics() { private class FastQuadReader implements Iterable { private final int quadCount = vertexCount / 4; + public void forEachPrimitive(Consumer action) { + MutableVertex[] reusableQuad = new MutableVertex[4]; + for (int i = 0; i < 4; i++) { + reusableQuad[i] = VertexData.mutable(); + } + for (int quadIndex = 0; quadIndex < quadCount; quadIndex++) { + fastReadQuadAt(quadIndex, reusableQuad); + action.accept(reusableQuad); + } + } + @Override public @NotNull Iterator iterator() { return new FastQuadIterator(); @@ -406,6 +530,18 @@ public Spliterator spliterator() { return new FastQuadSpliterator(0, quadCount); } + private void fastReadQuadPositionUVAt(int index, MutableVertex[] quad) { + int vertexOffset = index * 144; + for (int i = 0; i < 4; i++, vertexOffset += 36) { + MutableVertex mutable = quad[i]; + mutable.x = buffer.getFloat(vertexOffset); + mutable.y = buffer.getFloat(vertexOffset + 4); + mutable.z = buffer.getFloat(vertexOffset + 8); + mutable.u = buffer.getFloat(vertexOffset + 16); + mutable.v = buffer.getFloat(vertexOffset + 20); + } + } + private void fastReadQuadAt(int index, MutableVertex[] quad) { int vertexOffset = index * 144; for (int i = 0; i < 4; i++, vertexOffset += 36) { @@ -495,3 +631,4 @@ public int characteristics() { } } } + From d9d68c57a4b1e7d4fd70f5c5695b2e4dde317ed5 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Tue, 10 Mar 2026 02:19:53 +0800 Subject: [PATCH 2/2] Drop ModConfig newline-only change --- .../src/main/java/com/xtracr/realcamera/config/ModConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java b/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java index 45b2c85..da03938 100644 --- a/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java +++ b/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java @@ -330,4 +330,4 @@ private void clamp() { } } } -} +} \ No newline at end of file