Сомнительные оптимизации

This commit is contained in:
2026-08-08 04:22:51 +04:00
parent 1bcbcf6d60
commit e39c3d716d
5 changed files with 379 additions and 314 deletions
@@ -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)