From 4222e5d0bd0829658541a2b1e679f93ba0ff34eb Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 24 Jan 2025 17:02:25 -0600 Subject: [PATCH] Allow configuring the Radix sort to be stable The radix sort is inherently stable, but the fallback sort it uses on small arrays (or when memory cannot be allocated) was not. This fixes issues with Banner rendering in Minecraft, since the model uses many identical overlapping cuboids, which must retain their draw order. --- .../bsp_tree/InnerPartitionBSPNode.java | 2 +- .../data/DynamicTopoData.java | 2 +- .../data/StaticNormalRelativeData.java | 4 +-- .../sodium/client/util/sorting/RadixSort.java | 31 ++++++++++++------- .../client/util/sorting/VertexSorters.java | 4 +-- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 613dff0616..b2b3ef5fa7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -542,7 +542,7 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra perm[i] = i; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int i = 0; i < indexCount; i++) { perm[i] = indexBuffer[perm[i]]; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index 2156bb3283..7dfb9086e6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -232,7 +232,7 @@ static void distanceSortDirect(IntBuffer indexBuffer, TQuad[] quads, Vector3fc c perm[idx] = idx; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int idx = 0; idx < quads.length; idx++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java index 9998e67ce9..87ea467464 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java @@ -55,7 +55,7 @@ private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts, perm[q] = q; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int i = 0; i < quads.length; i++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[i]); @@ -102,7 +102,7 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, perm[idx] = idx; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int idx = 0; idx < count; idx++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java index f96a056572..36dddff6c3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java @@ -33,26 +33,29 @@ private static void prefixSums(int[][] offsets) { } /** - *

Sorts the specified array according to the natural ascending order using an unstable, out-of-place, indirect, - * 256-way LSD radix sort.

- * - *

This algorithm is well suited for large arrays of integers, especially when they are uniformly distributed - * over the entire range of the data type.

+ *

Sorts the specified array according to the natural ascending order using an out-of-place, indirect, + * 256-way LSD radix sort. This algorithm is well suited for large arrays of integers, especially when they are + * uniformly distributed over the entire range of the data type.

* *

This method implements an indirect sort. The elements of {@param perm} (which must be * exactly the numbers in the interval {@code [0..perm.length)}) will be permuted so that * {@code x[perm[i]] < x[perm[i + 1]]}.

* - *

While this radix sort is very fast on larger arrays, there is a certain amount of fixed cost involved in + *

While this implementation is very fast on larger arrays, there is a certain amount of fixed cost involved in * computing the histogram and prefix sums. Because of this, a fallback algorithm (currently quick sort) is used * for very small arrays to ensure this method performs well for all inputs of all sizes.

* + *

If {@param stable} is true, the sorting algorithm is defined to be stable, and duplicate keys will be + * returned in the ordering given by {@param keys}. Using a stable sort is slower than an unstable sort, but the + * overall time complexity and memory requirements are the same.

+ * * @param perm a permutation array indexing {@param keys}. * @param keys the array of elements to be sorted. + * @param stable whether the sort is allowed to re-order duplicate keys */ - public static void sortIndirect(final int[] perm, final int[] keys) { + public static void sortIndirect(final int[] perm, final int[] keys, boolean stable) { if (perm.length <= RADIX_SORT_THRESHOLD) { - smallSort(perm, keys); + smallSort(perm, keys, stable); return; } @@ -64,7 +67,7 @@ public static void sortIndirect(final int[] perm, final int[] keys) { next = new int[perm.length]; } catch (OutOfMemoryError oom) { // Not enough memory to perform an out-of-place sort, so use an in-place alternative. - fallbackInPlaceSort(perm, keys); + fallbackSort(perm, keys, stable); return; } @@ -102,17 +105,21 @@ private static void sortIndirect(final int[] perm, } } - private static void smallSort(int[] perm, int[] keys) { + private static void smallSort(int[] perm, int[] keys, boolean stable) { if (perm.length <= 1) { return; } - fallbackInPlaceSort(perm, keys); + fallbackSort(perm, keys, stable); } // Fallback sorting method which is guaranteed to be in-place and not require additional memory. - private static void fallbackInPlaceSort(int[] perm, int[] keys) { + private static void fallbackSort(int[] perm, int[] keys, boolean stable) { IntArrays.quickSortIndirect(perm, keys); + + if (stable) { + IntArrays.stabilize(perm, keys); + } } private static int extractDigit(int key, int digit) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java index fab533b5cc..46f13ce057 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java @@ -39,7 +39,7 @@ private abstract static class AbstractSorter implements VertexSortingExtended { perm[index] = index; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, true); return perm; } @@ -134,7 +134,7 @@ public static int[] sort(ByteBuffer buffer, int vertexCount, int vertexStride, V pVertex2 += primitiveStride; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, true); return perm; }