Сомнительные оптимизации
This commit is contained in:
@@ -2,21 +2,15 @@ package su.divan2000.veila.client.render.fog;
|
|||||||
|
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.atomic.AtomicLongArray;
|
||||||
|
|
||||||
public final class FogChunkStreamer {
|
public final class FogChunkStreamer {
|
||||||
|
|
||||||
private enum ChunkStatus {
|
|
||||||
PENDING,
|
|
||||||
LOADED
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ConcurrentLinkedQueue<BlockChangeEvent> blockChangeQueue =
|
|
||||||
new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
private static final int TOTAL_CHUNKS =
|
private static final int TOTAL_CHUNKS =
|
||||||
FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z;
|
FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z;
|
||||||
|
|
||||||
|
private static final int TOTAL_WORDS = (TOTAL_CHUNKS + 63) / 64;
|
||||||
|
|
||||||
private static final int CHUNK_DATA_SIZE =
|
private static final int CHUNK_DATA_SIZE =
|
||||||
FogWorldVolume.CHUNK_SIZE *
|
FogWorldVolume.CHUNK_SIZE *
|
||||||
FogWorldVolume.SIZE_Y *
|
FogWorldVolume.SIZE_Y *
|
||||||
@@ -24,13 +18,26 @@ public final class FogChunkStreamer {
|
|||||||
|
|
||||||
private static final int MAX_CHUNKS_PER_TICK = 4;
|
private static final int MAX_CHUNKS_PER_TICK = 4;
|
||||||
|
|
||||||
private static final ChunkStatus[] statuses = new ChunkStatus[TOTAL_CHUNKS];
|
// Битовые маски вместо массивов статусов
|
||||||
private static final boolean[] readyToUpload = new boolean[TOTAL_CHUNKS];
|
private static final AtomicLongArray pendingMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
|
private static final AtomicLongArray readyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
|
|
||||||
|
// Pre-allocated массивы данных
|
||||||
private static final byte[][] preparedData = new byte[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
|
private static final byte[][] preparedData = new byte[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
|
||||||
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
|
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
|
||||||
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
|
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
|
||||||
|
|
||||||
|
// Ring buffer для block change events (без аллокаций)
|
||||||
|
private static final int EVENT_BUFFER_SIZE = 256;
|
||||||
|
private static final int[] eventChunkX = new int[EVENT_BUFFER_SIZE];
|
||||||
|
private static final int[] eventChunkZ = new int[EVENT_BUFFER_SIZE];
|
||||||
|
private static final int[] eventBlockX = new int[EVENT_BUFFER_SIZE];
|
||||||
|
private static final int[] eventBlockY = new int[EVENT_BUFFER_SIZE];
|
||||||
|
private static final int[] eventBlockZ = new int[EVENT_BUFFER_SIZE];
|
||||||
|
private static final BlockState[] eventState = new BlockState[EVENT_BUFFER_SIZE];
|
||||||
|
private static volatile int eventWriteIndex = 0;
|
||||||
|
private static volatile int eventReadIndex = 0;
|
||||||
|
|
||||||
private static volatile int currentOriginChunkX;
|
private static volatile int currentOriginChunkX;
|
||||||
private static volatile int currentOriginChunkZ;
|
private static volatile int currentOriginChunkZ;
|
||||||
private static volatile int currentRingChunkOffsetX;
|
private static volatile int currentRingChunkOffsetX;
|
||||||
@@ -45,33 +52,81 @@ public final class FogChunkStreamer {
|
|||||||
private static final int[] regionDepth = new int[MAX_REGIONS];
|
private static final int[] regionDepth = new int[MAX_REGIONS];
|
||||||
private static int regionCount = 0;
|
private static int regionCount = 0;
|
||||||
|
|
||||||
|
// Pre-allocated temp массивы
|
||||||
|
private static final int[] tempXStarts = new int[2];
|
||||||
|
private static final int[] tempXCounts = new int[2];
|
||||||
|
private static final int[] tempZStarts = new int[2];
|
||||||
|
private static final int[] tempZCounts = new int[2];
|
||||||
|
|
||||||
private FogChunkStreamer() {
|
private FogChunkStreamer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record BlockChangeEvent(
|
// ======================== Утилиты ========================
|
||||||
int chunkX,
|
|
||||||
int chunkZ,
|
private static int floorMod(int x, int y) {
|
||||||
int blockX,
|
int r = x % y;
|
||||||
int blockY,
|
return r < 0 ? r + y : r;
|
||||||
int blockZ,
|
}
|
||||||
BlockState newState
|
|
||||||
) {}
|
private static void setBit(AtomicLongArray mask, int index) {
|
||||||
|
int wordIndex = index >> 6;
|
||||||
|
long bitMask = 1L << (index & 63);
|
||||||
|
while (true) {
|
||||||
|
long current = mask.get(wordIndex);
|
||||||
|
if ((current & bitMask) != 0) return;
|
||||||
|
if (mask.compareAndSet(wordIndex, current, current | bitMask)) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clearBit(AtomicLongArray mask, int index) {
|
||||||
|
int wordIndex = index >> 6;
|
||||||
|
long bitMask = 1L << (index & 63);
|
||||||
|
while (true) {
|
||||||
|
long current = mask.get(wordIndex);
|
||||||
|
if ((current & bitMask) == 0) return;
|
||||||
|
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
||||||
|
for (int i = startIndex; i < TOTAL_CHUNKS; i++) {
|
||||||
|
int wordIndex = i >> 6;
|
||||||
|
long bitMask = 1L << (i & 63);
|
||||||
|
if ((mask.get(wordIndex) & bitMask) != 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== События ========================
|
||||||
|
|
||||||
public static void onBlockChanged(
|
public static void onBlockChanged(
|
||||||
int chunkX, int chunkZ,
|
int chunkX, int chunkZ,
|
||||||
int blockX, int blockY, int blockZ,
|
int blockX, int blockY, int blockZ,
|
||||||
BlockState newState
|
BlockState newState
|
||||||
) {
|
) {
|
||||||
blockChangeQueue.add(new BlockChangeEvent(chunkX, chunkZ, blockX, blockY, blockZ, newState));
|
int nextWrite = (eventWriteIndex + 1) % EVENT_BUFFER_SIZE;
|
||||||
|
if (nextWrite == eventReadIndex) {
|
||||||
|
// Буфер переполнен — пропускаем событие (или можно расширить буфер)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventChunkX[eventWriteIndex] = chunkX;
|
||||||
|
eventChunkZ[eventWriteIndex] = chunkZ;
|
||||||
|
eventBlockX[eventWriteIndex] = blockX;
|
||||||
|
eventBlockY[eventWriteIndex] = blockY;
|
||||||
|
eventBlockZ[eventWriteIndex] = blockZ;
|
||||||
|
eventState[eventWriteIndex] = newState;
|
||||||
|
eventWriteIndex = nextWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== Окно ========================
|
||||||
|
|
||||||
public static void update(
|
public static void update(
|
||||||
int oldOriginChunkX,
|
int oldOriginChunkX, int oldOriginChunkZ,
|
||||||
int oldOriginChunkZ,
|
int newOriginChunkX, int newOriginChunkZ,
|
||||||
int newOriginChunkX,
|
int ringChunkOffsetX, int ringChunkOffsetZ
|
||||||
int newOriginChunkZ,
|
|
||||||
int ringChunkOffsetX,
|
|
||||||
int ringChunkOffsetZ
|
|
||||||
) {
|
) {
|
||||||
currentOriginChunkX = newOriginChunkX;
|
currentOriginChunkX = newOriginChunkX;
|
||||||
currentOriginChunkZ = newOriginChunkZ;
|
currentOriginChunkZ = newOriginChunkZ;
|
||||||
@@ -79,7 +134,7 @@ public final class FogChunkStreamer {
|
|||||||
currentRingChunkOffsetZ = ringChunkOffsetZ;
|
currentRingChunkOffsetZ = ringChunkOffsetZ;
|
||||||
|
|
||||||
if (firstUpdate) {
|
if (firstUpdate) {
|
||||||
markAllPending(newOriginChunkX, newOriginChunkZ);
|
markAllPending();
|
||||||
firstUpdate = false;
|
firstUpdate = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -94,7 +149,7 @@ public final class FogChunkStreamer {
|
|||||||
|
|
||||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||||
markAllPending(newOriginChunkX, newOriginChunkZ);
|
markAllPending();
|
||||||
uploadReadyData();
|
uploadReadyData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -137,39 +192,62 @@ public final class FogChunkStreamer {
|
|||||||
uploadReadyData();
|
uploadReadyData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectRegions(
|
private static void markPendingRange(int worldX, int worldZ, int countX, int countZ) {
|
||||||
int worldX, int worldZ,
|
for (int i = 0; i < countX; i++) {
|
||||||
int countX, int countZ
|
int wx = worldX + i;
|
||||||
) {
|
for (int j = 0; j < countZ; j++) {
|
||||||
int[] xStarts = new int[2];
|
int wz = worldZ + j;
|
||||||
int[] xCounts = new int[2];
|
int physicalX = floorMod(wx, FogWorldVolume.CHUNKS_X);
|
||||||
int xSegCount = splitIntoSegments(worldX, countX, FogWorldVolume.CHUNKS_X, xStarts, xCounts);
|
int physicalZ = floorMod(wz, FogWorldVolume.CHUNKS_Z);
|
||||||
|
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
|
||||||
|
clearBit(readyMask, physicalIndex);
|
||||||
|
setBit(pendingMask, physicalIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int[] zStarts = new int[2];
|
private static void markAllPending() {
|
||||||
int[] zCounts = new int[2];
|
regionCount = 1;
|
||||||
int zSegCount = splitIntoSegments(worldZ, countZ, FogWorldVolume.CHUNKS_Z, zStarts, zCounts);
|
regionPhysX[0] = 0;
|
||||||
|
regionPhysZ[0] = 0;
|
||||||
|
regionWidth[0] = FogWorldVolume.CHUNKS_X;
|
||||||
|
regionDepth[0] = FogWorldVolume.CHUNKS_Z;
|
||||||
|
|
||||||
|
for (int i = 0; i < TOTAL_WORDS; i++) {
|
||||||
|
pendingMask.set(i, -1L);
|
||||||
|
readyMask.set(i, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
int extraBits = TOTAL_CHUNKS & 63;
|
||||||
|
if (extraBits != 0) {
|
||||||
|
long mask = (1L << extraBits) - 1;
|
||||||
|
pendingMask.set(TOTAL_WORDS - 1, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||||
|
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
|
||||||
|
int xSegCount = splitIntoSegments(worldX, countX, FogWorldVolume.CHUNKS_X, tempXStarts, tempXCounts);
|
||||||
|
int zSegCount = splitIntoSegments(worldZ, countZ, FogWorldVolume.CHUNKS_Z, tempZStarts, tempZCounts);
|
||||||
|
|
||||||
for (int i = 0; i < xSegCount; i++) {
|
for (int i = 0; i < xSegCount; i++) {
|
||||||
for (int j = 0; j < zSegCount; j++) {
|
for (int j = 0; j < zSegCount; j++) {
|
||||||
if (regionCount >= MAX_REGIONS) return;
|
if (regionCount >= MAX_REGIONS) return;
|
||||||
regionPhysX[regionCount] = xStarts[i];
|
regionPhysX[regionCount] = tempXStarts[i];
|
||||||
regionPhysZ[regionCount] = zStarts[j];
|
regionPhysZ[regionCount] = tempZStarts[j];
|
||||||
regionWidth[regionCount] = xCounts[i];
|
regionWidth[regionCount] = tempXCounts[i];
|
||||||
regionDepth[regionCount] = zCounts[j];
|
regionDepth[regionCount] = tempZCounts[j];
|
||||||
regionCount++;
|
regionCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int splitIntoSegments(
|
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
|
||||||
int start, int count, int dim,
|
|
||||||
int[] outStarts, int[] outCounts
|
|
||||||
) {
|
|
||||||
if (count == 0) return 0;
|
if (count == 0) return 0;
|
||||||
|
int first = floorMod(start, dim);
|
||||||
int first = Math.floorMod(start, dim);
|
int last = floorMod(start + count - 1, dim);
|
||||||
int last = Math.floorMod(start + count - 1, dim);
|
|
||||||
|
|
||||||
if (first <= last) {
|
if (first <= last) {
|
||||||
outStarts[0] = first;
|
outStarts[0] = first;
|
||||||
outCounts[0] = count;
|
outCounts[0] = count;
|
||||||
@@ -184,75 +262,47 @@ public final class FogChunkStreamer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void markPendingRange(
|
// ======================== Обработка ========================
|
||||||
int worldX, int worldZ,
|
|
||||||
int countX, int countZ
|
|
||||||
) {
|
|
||||||
for (int i = 0; i < countX; i++) {
|
|
||||||
for (int j = 0; j < countZ; j++) {
|
|
||||||
int wx = worldX + i;
|
|
||||||
int wz = worldZ + j;
|
|
||||||
int physicalX = Math.floorMod(wx, FogWorldVolume.CHUNKS_X);
|
|
||||||
int physicalZ = Math.floorMod(wz, FogWorldVolume.CHUNKS_Z);
|
|
||||||
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
|
|
||||||
statuses[physicalIndex] = ChunkStatus.PENDING;
|
|
||||||
readyToUpload[physicalIndex] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void markAllPending(int originChunkX, int originChunkZ) {
|
|
||||||
regionCount = 1;
|
|
||||||
regionPhysX[0] = 0;
|
|
||||||
regionPhysZ[0] = 0;
|
|
||||||
regionWidth[0] = FogWorldVolume.CHUNKS_X;
|
|
||||||
regionDepth[0] = FogWorldVolume.CHUNKS_Z;
|
|
||||||
|
|
||||||
for (int i = 0; i < TOTAL_CHUNKS; i++) {
|
|
||||||
statuses[i] = ChunkStatus.PENDING;
|
|
||||||
readyToUpload[i] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
|
||||||
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void processBlockChanges() {
|
public static void processBlockChanges() {
|
||||||
BlockChangeEvent event;
|
|
||||||
while ((event = blockChangeQueue.poll()) != null) {
|
|
||||||
processSingleBlockChange(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void processSingleBlockChange(BlockChangeEvent event) {
|
|
||||||
int originX = currentOriginChunkX;
|
int originX = currentOriginChunkX;
|
||||||
int originZ = currentOriginChunkZ;
|
int originZ = currentOriginChunkZ;
|
||||||
int ringOffsetX = currentRingChunkOffsetX;
|
int ringOffsetX = currentRingChunkOffsetX;
|
||||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||||
|
|
||||||
int relativeX = event.chunkX() - originX;
|
while (eventReadIndex != eventWriteIndex) {
|
||||||
int relativeZ = event.chunkZ() - originZ;
|
int chunkX = eventChunkX[eventReadIndex];
|
||||||
|
int chunkZ = eventChunkZ[eventReadIndex];
|
||||||
|
int blockX = eventBlockX[eventReadIndex];
|
||||||
|
int blockY = eventBlockY[eventReadIndex];
|
||||||
|
int blockZ = eventBlockZ[eventReadIndex];
|
||||||
|
BlockState newState = eventState[eventReadIndex];
|
||||||
|
|
||||||
|
eventReadIndex = (eventReadIndex + 1) % EVENT_BUFFER_SIZE;
|
||||||
|
|
||||||
|
int relativeX = chunkX - originX;
|
||||||
|
int relativeZ = chunkZ - originZ;
|
||||||
|
|
||||||
if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X ||
|
if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X ||
|
||||||
relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) {
|
relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int physicalX = Math.floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
|
int physicalX = floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||||
int physicalZ = Math.floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
int physicalZ = floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||||
|
|
||||||
int localX = event.blockX() & 15;
|
int localX = blockX & 15;
|
||||||
int localZ = event.blockZ() & 15;
|
int localZ = blockZ & 15;
|
||||||
int yIndex = event.blockY() - FogWorldVolume.MIN_Y;
|
int yIndex = blockY - FogWorldVolume.MIN_Y;
|
||||||
|
|
||||||
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
|
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte value = FogChunkProvider.computePackedVoxelValue(event.newState());
|
byte value = FogChunkProvider.computePackedVoxelValue(newState);
|
||||||
|
|
||||||
FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value);
|
FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void processPending() {
|
public static void processPending() {
|
||||||
int originX = currentOriginChunkX;
|
int originX = currentOriginChunkX;
|
||||||
@@ -261,34 +311,26 @@ public final class FogChunkStreamer {
|
|||||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||||
|
|
||||||
int processedCount = 0;
|
int processedCount = 0;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) {
|
while (processedCount < MAX_CHUNKS_PER_TICK) {
|
||||||
if (statuses[physicalIndex] != ChunkStatus.PENDING) {
|
index = nextSetBit(pendingMask, index);
|
||||||
continue;
|
if (index < 0) break;
|
||||||
}
|
|
||||||
|
|
||||||
if (processedCount >= MAX_CHUNKS_PER_TICK) {
|
int physicalX = index % FogWorldVolume.CHUNKS_X;
|
||||||
break;
|
int physicalZ = index / FogWorldVolume.CHUNKS_X;
|
||||||
}
|
|
||||||
|
|
||||||
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
|
int worldChunkX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||||
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
|
int worldChunkZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||||
|
|
||||||
int worldChunkX = originX + Math.floorMod(
|
if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[index])) {
|
||||||
physicalX - ringOffsetX,
|
preparedWorldChunkX[index] = worldChunkX;
|
||||||
FogWorldVolume.CHUNKS_X
|
preparedWorldChunkZ[index] = worldChunkZ;
|
||||||
);
|
setBit(readyMask, index);
|
||||||
int worldChunkZ = originZ + Math.floorMod(
|
clearBit(pendingMask, index);
|
||||||
physicalZ - ringOffsetZ,
|
|
||||||
FogWorldVolume.CHUNKS_Z
|
|
||||||
);
|
|
||||||
|
|
||||||
if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[physicalIndex])) {
|
|
||||||
preparedWorldChunkX[physicalIndex] = worldChunkX;
|
|
||||||
preparedWorldChunkZ[physicalIndex] = worldChunkZ;
|
|
||||||
readyToUpload[physicalIndex] = true;
|
|
||||||
processedCount++;
|
processedCount++;
|
||||||
}
|
}
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,36 +340,28 @@ public final class FogChunkStreamer {
|
|||||||
int ringOffsetX = currentRingChunkOffsetX;
|
int ringOffsetX = currentRingChunkOffsetX;
|
||||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||||
|
|
||||||
for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) {
|
int index = 0;
|
||||||
if (!readyToUpload[physicalIndex]) {
|
while (true) {
|
||||||
continue;
|
index = nextSetBit(readyMask, index);
|
||||||
|
if (index < 0) break;
|
||||||
|
|
||||||
|
int physicalX = index % FogWorldVolume.CHUNKS_X;
|
||||||
|
int physicalZ = index / FogWorldVolume.CHUNKS_X;
|
||||||
|
|
||||||
|
int currentWorldX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||||
|
int currentWorldZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||||
|
|
||||||
|
if (preparedWorldChunkX[index] == currentWorldX &&
|
||||||
|
preparedWorldChunkZ[index] == currentWorldZ) {
|
||||||
|
|
||||||
|
FogWorldVolume.uploadChunkFromArray(physicalX, physicalZ, preparedData[index]);
|
||||||
|
clearBit(readyMask, index);
|
||||||
|
} else {
|
||||||
|
// Координаты изменились — помечаем как pending заново
|
||||||
|
clearBit(readyMask, index);
|
||||||
|
setBit(pendingMask, index);
|
||||||
}
|
}
|
||||||
|
index++;
|
||||||
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
|
|
||||||
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
|
|
||||||
|
|
||||||
int currentWorldX = originX + Math.floorMod(
|
|
||||||
physicalX - ringOffsetX,
|
|
||||||
FogWorldVolume.CHUNKS_X
|
|
||||||
);
|
|
||||||
int currentWorldZ = originZ + Math.floorMod(
|
|
||||||
physicalZ - ringOffsetZ,
|
|
||||||
FogWorldVolume.CHUNKS_Z
|
|
||||||
);
|
|
||||||
|
|
||||||
if (preparedWorldChunkX[physicalIndex] == currentWorldX &&
|
|
||||||
preparedWorldChunkZ[physicalIndex] == currentWorldZ) {
|
|
||||||
|
|
||||||
FogWorldVolume.uploadChunkFromArray(
|
|
||||||
physicalX,
|
|
||||||
physicalZ,
|
|
||||||
preparedData[physicalIndex]
|
|
||||||
);
|
|
||||||
|
|
||||||
statuses[physicalIndex] = ChunkStatus.LOADED;
|
|
||||||
}
|
|
||||||
|
|
||||||
readyToUpload[physicalIndex] = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,60 +10,82 @@ public final class FogLightProvider {
|
|||||||
|
|
||||||
public static final int SECTION_DATA_SIZE = 16 * 16 * 16;
|
public static final int SECTION_DATA_SIZE = 16 * 16 * 16;
|
||||||
|
|
||||||
|
// Pre-allocated массивы для каждого индекса. Инициализируются лениво.
|
||||||
|
private static byte[][] blockLightData;
|
||||||
|
private static byte[][] skyLightData;
|
||||||
|
|
||||||
private FogLightProvider() {
|
private FogLightProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] fillBlockLightIntoArray(int sectionX, int sectionY, int sectionZ) {
|
public static void init() {
|
||||||
|
if (blockLightData != null) return;
|
||||||
|
int total = FogLightStreamer.TOTAL_SECTIONS;
|
||||||
|
blockLightData = new byte[total][SECTION_DATA_SIZE];
|
||||||
|
skyLightData = new byte[total][SECTION_DATA_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Читает block light напрямую в pre-allocated массив.
|
||||||
|
* Возвращает true если данные успешно прочитаны.
|
||||||
|
*/
|
||||||
|
public static boolean fillBlockLightIntoArray(int sectionX, int sectionY, int sectionZ, int index) {
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
if (client.world == null) return null;
|
if (client.world == null) return false;
|
||||||
|
|
||||||
LightingProvider provider = client.world.getLightingProvider();
|
LightingProvider provider = client.world.getLightingProvider();
|
||||||
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
|
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
|
||||||
if (blockProvider == null) return null;
|
if (blockProvider == null) return false;
|
||||||
|
|
||||||
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
||||||
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
|
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
|
||||||
|
if (blockArray == null) return false;
|
||||||
|
|
||||||
if (blockArray == null) return null;
|
byte[] result = blockLightData[index];
|
||||||
|
int idx = 0;
|
||||||
// СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal
|
|
||||||
byte[] result = new byte[SECTION_DATA_SIZE];
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
for (int localZ = 0; localZ < 16; localZ++) {
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
for (int localY = 0; localY < 16; localY++) {
|
for (int localY = 0; localY < 16; localY++) {
|
||||||
for (int localX = 0; localX < 16; localX++) {
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
result[index++] = (byte) (blockArray.get(localX, localY, localZ) * 17);
|
result[idx++] = (byte) (blockArray.get(localX, localY, localZ) * 17);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] fillSkyLightIntoArray(int sectionX, int sectionY, int sectionZ) {
|
/**
|
||||||
|
* Читает sky light напрямую в pre-allocated массив.
|
||||||
|
*/
|
||||||
|
public static boolean fillSkyLightIntoArray(int sectionX, int sectionY, int sectionZ, int index) {
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
if (client.world == null) return null;
|
if (client.world == null) return false;
|
||||||
|
|
||||||
LightingProvider provider = client.world.getLightingProvider();
|
LightingProvider provider = client.world.getLightingProvider();
|
||||||
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider;
|
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider;
|
||||||
if (skyProvider == null) return null;
|
if (skyProvider == null) return false;
|
||||||
|
|
||||||
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
||||||
ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos);
|
ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos);
|
||||||
|
if (skyArray == null) return false;
|
||||||
|
|
||||||
if (skyArray == null) return null;
|
byte[] result = skyLightData[index];
|
||||||
|
int idx = 0;
|
||||||
// СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal
|
|
||||||
byte[] result = new byte[SECTION_DATA_SIZE];
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
for (int localZ = 0; localZ < 16; localZ++) {
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
for (int localY = 0; localY < 16; localY++) {
|
for (int localY = 0; localY < 16; localY++) {
|
||||||
for (int localX = 0; localX < 16; localX++) {
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
result[index++] = (byte) (skyArray.get(localX, localY, localZ) * 17);
|
result[idx++] = (byte) (skyArray.get(localX, localY, localZ) * 17);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getBlockLightData(int index) {
|
||||||
|
return blockLightData[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getSkyLightData(int index) {
|
||||||
|
return skyLightData[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package su.divan2000.veila.client.render.fog;
|
package su.divan2000.veila.client.render.fog;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicLongArray;
|
import java.util.concurrent.atomic.AtomicLongArray;
|
||||||
|
|
||||||
public final class FogLightStreamer {
|
public final class FogLightStreamer {
|
||||||
@@ -8,30 +7,30 @@ public final class FogLightStreamer {
|
|||||||
public static final int SECTION_SIZE = 16;
|
public static final int SECTION_SIZE = 16;
|
||||||
public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE;
|
public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE;
|
||||||
|
|
||||||
private static final int TOTAL_SECTIONS =
|
static final int TOTAL_SECTIONS =
|
||||||
FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y;
|
FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y;
|
||||||
|
|
||||||
private static final AtomicLongArray pendingBlockMask =
|
private static final int TOTAL_WORDS = (TOTAL_SECTIONS + 63) / 64;
|
||||||
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
|
||||||
private static final AtomicLongArray pendingSkyMask =
|
|
||||||
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
|
||||||
|
|
||||||
private static final AtomicLongArray readyBlockMask =
|
private static final AtomicLongArray pendingBlockMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
private static final AtomicLongArray pendingSkyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
private static final AtomicLongArray readySkyMask =
|
private static final AtomicLongArray readyBlockMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
private static final AtomicLongArray readySkyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
|
private static final AtomicLongArray initialBlockMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
|
private static final AtomicLongArray initialSkyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||||
|
|
||||||
private static final AtomicLongArray initialBlockMask =
|
// Flat массивы для мировых координат
|
||||||
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
private static final int[] preparedBlockWorldX = new int[TOTAL_SECTIONS];
|
||||||
private static final AtomicLongArray initialSkyMask =
|
private static final int[] preparedBlockWorldY = new int[TOTAL_SECTIONS];
|
||||||
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
private static final int[] preparedBlockWorldZ = new int[TOTAL_SECTIONS];
|
||||||
|
|
||||||
// Храним данные вместе с мировыми координатами секции
|
private static final int[] preparedSkyWorldX = new int[TOTAL_SECTIONS];
|
||||||
private static final ConcurrentHashMap<Integer, PreparedSection> preparedBlockData = new ConcurrentHashMap<>();
|
private static final int[] preparedSkyWorldY = new int[TOTAL_SECTIONS];
|
||||||
private static final ConcurrentHashMap<Integer, PreparedSection> preparedSkyData = new ConcurrentHashMap<>();
|
private static final int[] preparedSkyWorldZ = new int[TOTAL_SECTIONS];
|
||||||
|
|
||||||
private static final int MAX_SECTIONS_PER_FRAME = 64;
|
// Более равномерное распределение: меньше initial за раз, больше incremental
|
||||||
private static final int MAX_INITIAL_SECTIONS_PER_TICK = 96;
|
private static final int MAX_INITIAL_SECTIONS_PER_TICK = 32; // Было 96
|
||||||
|
private static final int MAX_PENDING_SECTIONS_PER_TICK = 64; // Новый параметр
|
||||||
|
|
||||||
private static volatile int currentOriginChunkX;
|
private static volatile int currentOriginChunkX;
|
||||||
private static volatile int currentOriginChunkZ;
|
private static volatile int currentOriginChunkZ;
|
||||||
@@ -47,23 +46,26 @@ public final class FogLightStreamer {
|
|||||||
private static final int[] regionDepth = new int[MAX_REGIONS];
|
private static final int[] regionDepth = new int[MAX_REGIONS];
|
||||||
private static int regionCount = 0;
|
private static int regionCount = 0;
|
||||||
|
|
||||||
private static class PreparedSection {
|
// Pre-allocated temp массивы
|
||||||
final byte[] data;
|
private static final int[] tempXStarts = new int[2];
|
||||||
final int worldSectionX;
|
private static final int[] tempXCounts = new int[2];
|
||||||
final int worldSectionY;
|
private static final int[] tempZStarts = new int[2];
|
||||||
final int worldSectionZ;
|
private static final int[] tempZCounts = new int[2];
|
||||||
|
|
||||||
PreparedSection(byte[] data, int worldSectionX, int worldSectionY, int worldSectionZ) {
|
|
||||||
this.data = data;
|
|
||||||
this.worldSectionX = worldSectionX;
|
|
||||||
this.worldSectionY = worldSectionY;
|
|
||||||
this.worldSectionZ = worldSectionZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FogLightStreamer() {
|
private FogLightStreamer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
FogLightProvider.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== Утилиты ========================
|
||||||
|
|
||||||
|
private static int floorMod(int x, int y) {
|
||||||
|
int r = x % y;
|
||||||
|
return r < 0 ? r + y : r;
|
||||||
|
}
|
||||||
|
|
||||||
private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) {
|
private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) {
|
||||||
int relativeX = sectionX - currentOriginChunkX;
|
int relativeX = sectionX - currentOriginChunkX;
|
||||||
int relativeZ = sectionZ - currentOriginChunkZ;
|
int relativeZ = sectionZ - currentOriginChunkZ;
|
||||||
@@ -78,45 +80,51 @@ public final class FogLightStreamer {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int physicalX = Math.floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
int physicalX = floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
int physicalZ = Math.floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
int physicalZ = floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
return (localSectionY * FogLightVolume.CHUNKS_Z * FogLightVolume.CHUNKS_X)
|
return (localSectionY * FogLightVolume.CHUNKS_Z * FogLightVolume.CHUNKS_X)
|
||||||
+ (physicalZ * FogLightVolume.CHUNKS_X)
|
+ (physicalZ * FogLightVolume.CHUNKS_X)
|
||||||
+ physicalX;
|
+ physicalX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean setBit(AtomicLongArray mask, int index) {
|
private static void setBit(AtomicLongArray mask, int index) {
|
||||||
int wordIndex = index >> 6;
|
int wordIndex = index >> 6;
|
||||||
long bitMask = 1L << (index & 63);
|
long bitMask = 1L << (index & 63);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long current = mask.get(wordIndex);
|
long current = mask.get(wordIndex);
|
||||||
if ((current & bitMask) != 0) return false;
|
if ((current & bitMask) != 0) return;
|
||||||
if (mask.compareAndSet(wordIndex, current, current | bitMask)) return true;
|
if (mask.compareAndSet(wordIndex, current, current | bitMask)) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean clearBit(AtomicLongArray mask, int index) {
|
private static void clearBit(AtomicLongArray mask, int index) {
|
||||||
int wordIndex = index >> 6;
|
int wordIndex = index >> 6;
|
||||||
long bitMask = 1L << (index & 63);
|
long bitMask = 1L << (index & 63);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long current = mask.get(wordIndex);
|
long current = mask.get(wordIndex);
|
||||||
if ((current & bitMask) == 0) return false;
|
if ((current & bitMask) == 0) return;
|
||||||
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return true;
|
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Простой последовательный скан — работает быстрее на практике
|
||||||
|
* благодаря branch prediction и JIT-оптимизациям.
|
||||||
|
*/
|
||||||
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
||||||
for (int i = startIndex; i < TOTAL_SECTIONS; i++) {
|
for (int i = startIndex; i < TOTAL_SECTIONS; i++) {
|
||||||
int wordIndex = i >> 6;
|
int wordIndex = i >> 6;
|
||||||
long bitMask = 1L << (i & 63);
|
long bitMask = 1L << (i & 63);
|
||||||
if ((mask.get(wordIndex) & bitMask) != 0) return i;
|
if ((mask.get(wordIndex) & bitMask) != 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================== События обновления ========================
|
||||||
|
|
||||||
public static void onBlockLightUpdated(long sectionPos) {
|
public static void onBlockLightUpdated(long sectionPos) {
|
||||||
int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos);
|
int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos);
|
||||||
int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos);
|
int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos);
|
||||||
@@ -124,7 +132,7 @@ public final class FogLightStreamer {
|
|||||||
|
|
||||||
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
preparedBlockData.remove(index);
|
clearBit(readyBlockMask, index);
|
||||||
setBit(pendingBlockMask, index);
|
setBit(pendingBlockMask, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,11 +144,13 @@ public final class FogLightStreamer {
|
|||||||
|
|
||||||
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
preparedSkyData.remove(index);
|
clearBit(readySkyMask, index);
|
||||||
setBit(pendingSkyMask, index);
|
setBit(pendingSkyMask, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================== Окно ========================
|
||||||
|
|
||||||
public static void update(
|
public static void update(
|
||||||
int oldOriginChunkX, int oldOriginChunkZ,
|
int oldOriginChunkX, int oldOriginChunkZ,
|
||||||
int newOriginChunkX, int newOriginChunkZ,
|
int newOriginChunkX, int newOriginChunkZ,
|
||||||
@@ -194,10 +204,13 @@ public final class FogLightStreamer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) {
|
private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) {
|
||||||
|
int minY = FogWorldVolume.MIN_Y >> 4;
|
||||||
for (int i = 0; i < countX; i++) {
|
for (int i = 0; i < countX; i++) {
|
||||||
|
int wx = worldX + i;
|
||||||
for (int j = 0; j < countZ; j++) {
|
for (int j = 0; j < countZ; j++) {
|
||||||
|
int wz = worldZ + j;
|
||||||
for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) {
|
for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) {
|
||||||
int index = sectionToLocalIndex(worldX + i, sectionY + (FogWorldVolume.MIN_Y >> 4), worldZ + j);
|
int index = sectionToLocalIndex(wx, sectionY + minY, wz);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
setBit(initialBlockMask, index);
|
setBit(initialBlockMask, index);
|
||||||
setBit(initialSkyMask, index);
|
setBit(initialSkyMask, index);
|
||||||
@@ -216,38 +229,34 @@ public final class FogLightStreamer {
|
|||||||
|
|
||||||
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||||
|
|
||||||
preparedBlockData.clear();
|
for (int i = 0; i < TOTAL_WORDS; i++) {
|
||||||
preparedSkyData.clear();
|
pendingBlockMask.set(i, 0L);
|
||||||
|
pendingSkyMask.set(i, 0L);
|
||||||
for (int i = 0; i < pendingBlockMask.length(); i++) {
|
readyBlockMask.set(i, 0L);
|
||||||
|
readySkyMask.set(i, 0L);
|
||||||
initialBlockMask.set(i, -1L);
|
initialBlockMask.set(i, -1L);
|
||||||
initialSkyMask.set(i, -1L);
|
initialSkyMask.set(i, -1L);
|
||||||
}
|
}
|
||||||
int totalWords = (TOTAL_SECTIONS + 63) / 64;
|
|
||||||
int extraBits = TOTAL_SECTIONS & 63;
|
int extraBits = TOTAL_SECTIONS & 63;
|
||||||
if (extraBits != 0) {
|
if (extraBits != 0) {
|
||||||
long mask = (1L << extraBits) - 1;
|
long mask = (1L << extraBits) - 1;
|
||||||
initialBlockMask.set(totalWords - 1, mask);
|
initialBlockMask.set(TOTAL_WORDS - 1, mask);
|
||||||
initialSkyMask.set(totalWords - 1, mask);
|
initialSkyMask.set(TOTAL_WORDS - 1, mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
|
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
|
||||||
int[] xStarts = new int[2];
|
int xSegCount = splitIntoSegments(worldX, countX, FogLightVolume.CHUNKS_X, tempXStarts, tempXCounts);
|
||||||
int[] xCounts = new int[2];
|
int zSegCount = splitIntoSegments(worldZ, countZ, FogLightVolume.CHUNKS_Z, tempZStarts, tempZCounts);
|
||||||
int xSegCount = splitIntoSegments(worldX, countX, FogLightVolume.CHUNKS_X, xStarts, xCounts);
|
|
||||||
|
|
||||||
int[] zStarts = new int[2];
|
|
||||||
int[] zCounts = new int[2];
|
|
||||||
int zSegCount = splitIntoSegments(worldZ, countZ, FogLightVolume.CHUNKS_Z, zStarts, zCounts);
|
|
||||||
|
|
||||||
for (int i = 0; i < xSegCount; i++) {
|
for (int i = 0; i < xSegCount; i++) {
|
||||||
for (int j = 0; j < zSegCount; j++) {
|
for (int j = 0; j < zSegCount; j++) {
|
||||||
if (regionCount >= MAX_REGIONS) return;
|
if (regionCount >= MAX_REGIONS) return;
|
||||||
regionPhysX[regionCount] = xStarts[i];
|
regionPhysX[regionCount] = tempXStarts[i];
|
||||||
regionPhysZ[regionCount] = zStarts[j];
|
regionPhysZ[regionCount] = tempZStarts[j];
|
||||||
regionWidth[regionCount] = xCounts[i];
|
regionWidth[regionCount] = tempXCounts[i];
|
||||||
regionDepth[regionCount] = zCounts[j];
|
regionDepth[regionCount] = tempZCounts[j];
|
||||||
regionCount++;
|
regionCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,8 +264,8 @@ public final class FogLightStreamer {
|
|||||||
|
|
||||||
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
|
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
|
||||||
if (count == 0) return 0;
|
if (count == 0) return 0;
|
||||||
int first = Math.floorMod(start, dim);
|
int first = floorMod(start, dim);
|
||||||
int last = Math.floorMod(start + count - 1, dim);
|
int last = floorMod(start + count - 1, dim);
|
||||||
if (first <= last) {
|
if (first <= last) {
|
||||||
outStarts[0] = first;
|
outStarts[0] = first;
|
||||||
outCounts[0] = count;
|
outCounts[0] = count;
|
||||||
@@ -271,53 +280,58 @@ public final class FogLightStreamer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void prepareAllSections() {
|
// ======================== Подготовка и загрузка ========================
|
||||||
int processed = 0;
|
|
||||||
|
|
||||||
// Initial load - block light
|
/**
|
||||||
|
* Более равномерное распределение нагрузки:
|
||||||
|
* обрабатываем и initial, и pending параллельно, чтобы избежать пиков.
|
||||||
|
*/
|
||||||
|
public static void prepareAllSections() {
|
||||||
|
int processedBlock = 0;
|
||||||
|
int processedSky = 0;
|
||||||
|
|
||||||
|
// Чередование: немного initial block, немного pending block,
|
||||||
|
// немного initial sky, немного pending sky
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (processed < MAX_INITIAL_SECTIONS_PER_TICK) {
|
while (processedBlock < MAX_INITIAL_SECTIONS_PER_TICK) {
|
||||||
index = nextSetBit(initialBlockMask, index);
|
index = nextSetBit(initialBlockMask, index);
|
||||||
if (index < 0) break;
|
if (index < 0) break;
|
||||||
if (prepareBlockLight(index)) {
|
if (prepareBlockLight(index)) {
|
||||||
clearBit(initialBlockMask, index);
|
clearBit(initialBlockMask, index);
|
||||||
processed++;
|
processedBlock++;
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial load - sky light
|
|
||||||
index = 0;
|
index = 0;
|
||||||
while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2) {
|
while (processedBlock < MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK) {
|
||||||
index = nextSetBit(initialSkyMask, index);
|
|
||||||
if (index < 0) break;
|
|
||||||
if (prepareSkyLight(index)) {
|
|
||||||
clearBit(initialSkyMask, index);
|
|
||||||
processed++;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incremental - block light
|
|
||||||
index = 0;
|
|
||||||
while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2 + MAX_SECTIONS_PER_FRAME) {
|
|
||||||
index = nextSetBit(pendingBlockMask, index);
|
index = nextSetBit(pendingBlockMask, index);
|
||||||
if (index < 0) break;
|
if (index < 0) break;
|
||||||
if (prepareBlockLight(index)) {
|
if (prepareBlockLight(index)) {
|
||||||
clearBit(pendingBlockMask, index);
|
clearBit(pendingBlockMask, index);
|
||||||
processed++;
|
processedBlock++;
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incremental - sky light
|
|
||||||
index = 0;
|
index = 0;
|
||||||
while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2 + MAX_SECTIONS_PER_FRAME * 2) {
|
while (processedSky < MAX_INITIAL_SECTIONS_PER_TICK) {
|
||||||
|
index = nextSetBit(initialSkyMask, index);
|
||||||
|
if (index < 0) break;
|
||||||
|
if (prepareSkyLight(index)) {
|
||||||
|
clearBit(initialSkyMask, index);
|
||||||
|
processedSky++;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
while (processedSky < MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK) {
|
||||||
index = nextSetBit(pendingSkyMask, index);
|
index = nextSetBit(pendingSkyMask, index);
|
||||||
if (index < 0) break;
|
if (index < 0) break;
|
||||||
if (prepareSkyLight(index)) {
|
if (prepareSkyLight(index)) {
|
||||||
clearBit(pendingSkyMask, index);
|
clearBit(pendingSkyMask, index);
|
||||||
processed++;
|
processedSky++;
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@@ -327,22 +341,20 @@ public final class FogLightStreamer {
|
|||||||
int processed = 0;
|
int processed = 0;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
while (processed < MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) {
|
while (processed < MAX_PENDING_SECTIONS_PER_TICK) {
|
||||||
index = nextSetBit(readyBlockMask, index);
|
index = nextSetBit(readyBlockMask, index);
|
||||||
if (index < 0) break;
|
if (index < 0) break;
|
||||||
if (uploadBlockLight(index)) {
|
if (uploadBlockLight(index)) {
|
||||||
clearBit(readyBlockMask, index);
|
|
||||||
processed++;
|
processed++;
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
index = 0;
|
index = 0;
|
||||||
while (processed < (MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) * 2) {
|
while (processed < MAX_PENDING_SECTIONS_PER_TICK * 2) {
|
||||||
index = nextSetBit(readySkyMask, index);
|
index = nextSetBit(readySkyMask, index);
|
||||||
if (index < 0) break;
|
if (index < 0) break;
|
||||||
if (uploadSkyLight(index)) {
|
if (uploadSkyLight(index)) {
|
||||||
clearBit(readySkyMask, index);
|
|
||||||
processed++;
|
processed++;
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
@@ -350,21 +362,22 @@ public final class FogLightStreamer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean prepareBlockLight(int index) {
|
private static boolean prepareBlockLight(int index) {
|
||||||
// Извлекаем физические координаты
|
|
||||||
int physicalX = index % FogLightVolume.CHUNKS_X;
|
int physicalX = index % FogLightVolume.CHUNKS_X;
|
||||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
// Конвертируем в мировые координаты
|
int sectionX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
int sectionX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
|
||||||
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
|
||||||
byte[] data = FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ);
|
if (!FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ, index)) {
|
||||||
if (data == null) return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
preparedBlockWorldX[index] = sectionX;
|
||||||
|
preparedBlockWorldY[index] = sectionY;
|
||||||
|
preparedBlockWorldZ[index] = sectionZ;
|
||||||
|
|
||||||
// Сохраняем данные вместе с мировыми координатами
|
|
||||||
preparedBlockData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ));
|
|
||||||
setBit(readyBlockMask, index);
|
setBit(readyBlockMask, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -374,14 +387,18 @@ public final class FogLightStreamer {
|
|||||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
int sectionX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
int sectionX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
|
||||||
byte[] data = FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ);
|
if (!FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ, index)) {
|
||||||
if (data == null) return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
preparedSkyWorldX[index] = sectionX;
|
||||||
|
preparedSkyWorldY[index] = sectionY;
|
||||||
|
preparedSkyWorldZ[index] = sectionZ;
|
||||||
|
|
||||||
preparedSkyData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ));
|
|
||||||
setBit(readySkyMask, index);
|
setBit(readySkyMask, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -391,27 +408,21 @@ public final class FogLightStreamer {
|
|||||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
PreparedSection prepared = preparedBlockData.remove(index);
|
int currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
if (prepared == null) {
|
int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
setBit(pendingBlockMask, index);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вычисляем текущие мировые координаты для этой физической позиции
|
|
||||||
int currentWorldX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
|
||||||
int currentWorldZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
|
||||||
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
|
||||||
// Проверяем, что мировые координаты не изменились
|
if (preparedBlockWorldX[index] != currentWorldX ||
|
||||||
if (prepared.worldSectionX != currentWorldX ||
|
preparedBlockWorldY[index] != currentWorldY ||
|
||||||
prepared.worldSectionY != currentWorldY ||
|
preparedBlockWorldZ[index] != currentWorldZ) {
|
||||||
prepared.worldSectionZ != currentWorldZ) {
|
clearBit(readyBlockMask, index);
|
||||||
// Координаты изменились — отбрасываем данные и помечаем как pending
|
|
||||||
setBit(pendingBlockMask, index);
|
setBit(pendingBlockMask, index);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FogLightVolume.uploadBlockLightSection(physicalX, localSectionY, physicalZ, prepared.data);
|
FogLightVolume.uploadBlockLightSection(physicalX, localSectionY, physicalZ,
|
||||||
|
FogLightProvider.getBlockLightData(index));
|
||||||
|
clearBit(readyBlockMask, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,24 +431,21 @@ public final class FogLightStreamer {
|
|||||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
PreparedSection prepared = preparedSkyData.remove(index);
|
int currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
if (prepared == null) {
|
int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
setBit(pendingSkyMask, index);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int currentWorldX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
|
||||||
int currentWorldZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
|
||||||
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
|
||||||
if (prepared.worldSectionX != currentWorldX ||
|
if (preparedSkyWorldX[index] != currentWorldX ||
|
||||||
prepared.worldSectionY != currentWorldY ||
|
preparedSkyWorldY[index] != currentWorldY ||
|
||||||
prepared.worldSectionZ != currentWorldZ) {
|
preparedSkyWorldZ[index] != currentWorldZ) {
|
||||||
|
clearBit(readySkyMask, index);
|
||||||
setBit(pendingSkyMask, index);
|
setBit(pendingSkyMask, index);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FogLightVolume.uploadSkyLightSection(physicalX, localSectionY, physicalZ, prepared.data);
|
FogLightVolume.uploadSkyLightSection(physicalX, localSectionY, physicalZ,
|
||||||
|
FogLightProvider.getSkyLightData(index));
|
||||||
|
clearBit(readySkyMask, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,7 @@ public final class FogSystem {
|
|||||||
private static void initialize() {
|
private static void initialize() {
|
||||||
FogWorldVolume.init();
|
FogWorldVolume.init();
|
||||||
FogLightVolume.init();
|
FogLightVolume.init();
|
||||||
|
FogLightStreamer.init();
|
||||||
FogSimulationManager.init();
|
FogSimulationManager.init();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ float density(vec3 worldPos)
|
|||||||
{
|
{
|
||||||
vec3 uv = worldToUV(worldPos);
|
vec3 uv = worldToUV(worldPos);
|
||||||
if (uv.x < 0.0) return 0.0;
|
if (uv.x < 0.0) return 0.0;
|
||||||
return max(texture(FogVolume, uv).r, AMBIENT_FOG);
|
return max(texture(BlockLightVolume, uv).r, 1.0-texture(SkyLightVolume, uv).r)*0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 light(vec3 worldPos)
|
vec2 light(vec3 worldPos)
|
||||||
|
|||||||
Reference in New Issue
Block a user