From e39c3d716d79b800f5841419f7ea9bbe6e943b20 Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Sat, 8 Aug 2026 04:22:51 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BE=D0=BC=D0=BD=D0=B8=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/render/fog/FogChunkStreamer.java | 354 ++++++++++-------- .../client/render/fog/FogLightProvider.java | 62 ++- .../client/render/fog/FogLightStreamer.java | 274 +++++++------- .../veila/client/render/fog/FogSystem.java | 1 + .../assets/veila/shaders/fog_raymarching.frag | 2 +- 5 files changed, 379 insertions(+), 314 deletions(-) diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java b/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java index 5134bf3..19621ec 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java @@ -2,21 +2,15 @@ package su.divan2000.veila.client.render.fog; import net.minecraft.block.BlockState; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLongArray; public final class FogChunkStreamer { - private enum ChunkStatus { - PENDING, - LOADED - } - - private static final ConcurrentLinkedQueue blockChangeQueue = - new ConcurrentLinkedQueue<>(); - private static final int TOTAL_CHUNKS = FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z; + private static final int TOTAL_WORDS = (TOTAL_CHUNKS + 63) / 64; + private static final int CHUNK_DATA_SIZE = FogWorldVolume.CHUNK_SIZE * FogWorldVolume.SIZE_Y * @@ -24,13 +18,26 @@ public final class FogChunkStreamer { 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 int[] preparedWorldChunkX = 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 currentOriginChunkZ; private static volatile int currentRingChunkOffsetX; @@ -45,33 +52,81 @@ public final class FogChunkStreamer { private static final int[] regionDepth = new int[MAX_REGIONS]; 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 record BlockChangeEvent( - int chunkX, - int chunkZ, - int blockX, - int blockY, - int blockZ, - BlockState newState - ) {} + // ======================== Утилиты ======================== + + private static int floorMod(int x, int y) { + int r = x % y; + return r < 0 ? r + y : r; + } + + 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( int chunkX, int chunkZ, int blockX, int blockY, int blockZ, 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( - int oldOriginChunkX, - int oldOriginChunkZ, - int newOriginChunkX, - int newOriginChunkZ, - int ringChunkOffsetX, - int ringChunkOffsetZ + int oldOriginChunkX, int oldOriginChunkZ, + int newOriginChunkX, int newOriginChunkZ, + int ringChunkOffsetX, int ringChunkOffsetZ ) { currentOriginChunkX = newOriginChunkX; currentOriginChunkZ = newOriginChunkZ; @@ -79,7 +134,7 @@ public final class FogChunkStreamer { currentRingChunkOffsetZ = ringChunkOffsetZ; if (firstUpdate) { - markAllPending(newOriginChunkX, newOriginChunkZ); + markAllPending(); firstUpdate = false; return; } @@ -94,7 +149,7 @@ public final class FogChunkStreamer { if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X || Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) { - markAllPending(newOriginChunkX, newOriginChunkZ); + markAllPending(); uploadReadyData(); return; } @@ -137,39 +192,62 @@ public final class FogChunkStreamer { uploadReadyData(); } - private static void collectRegions( - int worldX, int worldZ, - int countX, int countZ - ) { - int[] xStarts = new int[2]; - int[] xCounts = new int[2]; - int xSegCount = splitIntoSegments(worldX, countX, FogWorldVolume.CHUNKS_X, xStarts, xCounts); + private static void markPendingRange(int worldX, int worldZ, int countX, int countZ) { + for (int i = 0; i < countX; i++) { + int wx = worldX + i; + for (int j = 0; j < countZ; j++) { + int wz = worldZ + j; + int physicalX = floorMod(wx, FogWorldVolume.CHUNKS_X); + 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]; - int[] zCounts = new int[2]; - int zSegCount = splitIntoSegments(worldZ, countZ, FogWorldVolume.CHUNKS_Z, zStarts, zCounts); + private static void markAllPending() { + regionCount = 1; + 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 j = 0; j < zSegCount; j++) { if (regionCount >= MAX_REGIONS) return; - regionPhysX[regionCount] = xStarts[i]; - regionPhysZ[regionCount] = zStarts[j]; - regionWidth[regionCount] = xCounts[i]; - regionDepth[regionCount] = zCounts[j]; + regionPhysX[regionCount] = tempXStarts[i]; + regionPhysZ[regionCount] = tempZStarts[j]; + regionWidth[regionCount] = tempXCounts[i]; + regionDepth[regionCount] = tempZCounts[j]; regionCount++; } } } - 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; - - int first = Math.floorMod(start, dim); - int last = Math.floorMod(start + count - 1, dim); - + int first = floorMod(start, dim); + int last = floorMod(start + count - 1, dim); if (first <= last) { outStarts[0] = first; outCounts[0] = count; @@ -184,74 +262,46 @@ 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() { - BlockChangeEvent event; - while ((event = blockChangeQueue.poll()) != null) { - processSingleBlockChange(event); - } - } - - private static void processSingleBlockChange(BlockChangeEvent event) { int originX = currentOriginChunkX; int originZ = currentOriginChunkZ; int ringOffsetX = currentRingChunkOffsetX; int ringOffsetZ = currentRingChunkOffsetZ; - int relativeX = event.chunkX() - originX; - int relativeZ = event.chunkZ() - originZ; + while (eventReadIndex != eventWriteIndex) { + int chunkX = eventChunkX[eventReadIndex]; + int chunkZ = eventChunkZ[eventReadIndex]; + int blockX = eventBlockX[eventReadIndex]; + int blockY = eventBlockY[eventReadIndex]; + int blockZ = eventBlockZ[eventReadIndex]; + BlockState newState = eventState[eventReadIndex]; - if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X || - relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) { - return; + eventReadIndex = (eventReadIndex + 1) % EVENT_BUFFER_SIZE; + + int relativeX = chunkX - originX; + int relativeZ = chunkZ - originZ; + + if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X || + relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) { + continue; + } + + int physicalX = floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X); + int physicalZ = floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z); + + int localX = blockX & 15; + int localZ = blockZ & 15; + int yIndex = blockY - FogWorldVolume.MIN_Y; + + if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) { + continue; + } + + byte value = FogChunkProvider.computePackedVoxelValue(newState); + FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value); } - - int physicalX = Math.floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X); - int physicalZ = Math.floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z); - - int localX = event.blockX() & 15; - int localZ = event.blockZ() & 15; - int yIndex = event.blockY() - FogWorldVolume.MIN_Y; - - if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) { - return; - } - - byte value = FogChunkProvider.computePackedVoxelValue(event.newState()); - - FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value); } public static void processPending() { @@ -261,34 +311,26 @@ public final class FogChunkStreamer { int ringOffsetZ = currentRingChunkOffsetZ; int processedCount = 0; + int index = 0; - for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) { - if (statuses[physicalIndex] != ChunkStatus.PENDING) { - continue; - } + while (processedCount < MAX_CHUNKS_PER_TICK) { + index = nextSetBit(pendingMask, index); + if (index < 0) break; - if (processedCount >= MAX_CHUNKS_PER_TICK) { - break; - } + int physicalX = index % FogWorldVolume.CHUNKS_X; + int physicalZ = index / FogWorldVolume.CHUNKS_X; - int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X; - int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X; + int worldChunkX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X); + int worldChunkZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z); - int worldChunkX = originX + Math.floorMod( - physicalX - ringOffsetX, - FogWorldVolume.CHUNKS_X - ); - int worldChunkZ = originZ + Math.floorMod( - physicalZ - ringOffsetZ, - FogWorldVolume.CHUNKS_Z - ); - - if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[physicalIndex])) { - preparedWorldChunkX[physicalIndex] = worldChunkX; - preparedWorldChunkZ[physicalIndex] = worldChunkZ; - readyToUpload[physicalIndex] = true; + if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[index])) { + preparedWorldChunkX[index] = worldChunkX; + preparedWorldChunkZ[index] = worldChunkZ; + setBit(readyMask, index); + clearBit(pendingMask, index); processedCount++; } + index++; } } @@ -298,36 +340,28 @@ public final class FogChunkStreamer { int ringOffsetX = currentRingChunkOffsetX; int ringOffsetZ = currentRingChunkOffsetZ; - for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) { - if (!readyToUpload[physicalIndex]) { - continue; + int index = 0; + while (true) { + 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); } - - 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; + index++; } } } \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java b/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java index 81f8d6a..9e3dcbc 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java @@ -10,60 +10,82 @@ public final class FogLightProvider { public static final int SECTION_DATA_SIZE = 16 * 16 * 16; + // Pre-allocated массивы для каждого индекса. Инициализируются лениво. + private static byte[][] blockLightData; + private static byte[][] skyLightData; + 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(); - if (client.world == null) return null; + if (client.world == null) return false; LightingProvider provider = client.world.getLightingProvider(); ChunkLightProvider blockProvider = provider.blockLightProvider; - if (blockProvider == null) return null; + if (blockProvider == null) return false; ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ); ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos); + if (blockArray == null) return false; - if (blockArray == null) return null; - - // СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal - byte[] result = new byte[SECTION_DATA_SIZE]; - int index = 0; + byte[] result = blockLightData[index]; + int idx = 0; for (int localZ = 0; localZ < 16; localZ++) { for (int localY = 0; localY < 16; localY++) { 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(); - if (client.world == null) return null; + if (client.world == null) return false; LightingProvider provider = client.world.getLightingProvider(); ChunkLightProvider skyProvider = provider.skyLightProvider; - if (skyProvider == null) return null; + if (skyProvider == null) return false; ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ); ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos); + if (skyArray == null) return false; - if (skyArray == null) return null; - - // СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal - byte[] result = new byte[SECTION_DATA_SIZE]; - int index = 0; + byte[] result = skyLightData[index]; + int idx = 0; for (int localZ = 0; localZ < 16; localZ++) { for (int localY = 0; localY < 16; localY++) { 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]; } } \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java b/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java index 78a3b80..b1e486c 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java @@ -1,6 +1,5 @@ package su.divan2000.veila.client.render.fog; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLongArray; public final class FogLightStreamer { @@ -8,30 +7,30 @@ public final class FogLightStreamer { public static final int SECTION_SIZE = 16; 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; - private static final AtomicLongArray pendingBlockMask = - new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); - private static final AtomicLongArray pendingSkyMask = - new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + private static final int TOTAL_WORDS = (TOTAL_SECTIONS + 63) / 64; - private static final AtomicLongArray readyBlockMask = - new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); - private static final AtomicLongArray readySkyMask = - new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + private static final AtomicLongArray pendingBlockMask = new AtomicLongArray(TOTAL_WORDS); + private static final AtomicLongArray pendingSkyMask = new AtomicLongArray(TOTAL_WORDS); + private static final AtomicLongArray readyBlockMask = new AtomicLongArray(TOTAL_WORDS); + 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 = - new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); - private static final AtomicLongArray initialSkyMask = - new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + // Flat массивы для мировых координат + private static final int[] preparedBlockWorldX = new int[TOTAL_SECTIONS]; + private static final int[] preparedBlockWorldY = new int[TOTAL_SECTIONS]; + private static final int[] preparedBlockWorldZ = new int[TOTAL_SECTIONS]; - // Храним данные вместе с мировыми координатами секции - private static final ConcurrentHashMap preparedBlockData = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap preparedSkyData = new ConcurrentHashMap<>(); + private static final int[] preparedSkyWorldX = new int[TOTAL_SECTIONS]; + private static final int[] preparedSkyWorldY = new int[TOTAL_SECTIONS]; + private static final int[] preparedSkyWorldZ = new int[TOTAL_SECTIONS]; - private static final int MAX_SECTIONS_PER_FRAME = 64; - private static final int MAX_INITIAL_SECTIONS_PER_TICK = 96; + // Более равномерное распределение: меньше initial за раз, больше incremental + 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 currentOriginChunkZ; @@ -47,23 +46,26 @@ public final class FogLightStreamer { private static final int[] regionDepth = new int[MAX_REGIONS]; private static int regionCount = 0; - private static class PreparedSection { - final byte[] data; - final int worldSectionX; - final int worldSectionY; - final int worldSectionZ; - - PreparedSection(byte[] data, int worldSectionX, int worldSectionY, int worldSectionZ) { - this.data = data; - this.worldSectionX = worldSectionX; - this.worldSectionY = worldSectionY; - this.worldSectionZ = worldSectionZ; - } - } + // 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 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) { int relativeX = sectionX - currentOriginChunkX; int relativeZ = sectionZ - currentOriginChunkZ; @@ -78,45 +80,51 @@ public final class FogLightStreamer { return -1; } - int physicalX = Math.floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); - int physicalZ = Math.floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); + int physicalX = floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int physicalZ = floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); return (localSectionY * FogLightVolume.CHUNKS_Z * FogLightVolume.CHUNKS_X) + (physicalZ * FogLightVolume.CHUNKS_X) + physicalX; } - private static boolean setBit(AtomicLongArray mask, int index) { + 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 false; - if (mask.compareAndSet(wordIndex, current, current | bitMask)) return true; + if ((current & bitMask) != 0) return; + 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; long bitMask = 1L << (index & 63); - while (true) { long current = mask.get(wordIndex); - if ((current & bitMask) == 0) return false; - if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return true; + if ((current & bitMask) == 0) return; + if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return; } } + /** + * Простой последовательный скан — работает быстрее на практике + * благодаря branch prediction и JIT-оптимизациям. + */ private static int nextSetBit(AtomicLongArray mask, int startIndex) { for (int i = startIndex; i < TOTAL_SECTIONS; i++) { int wordIndex = i >> 6; long bitMask = 1L << (i & 63); - if ((mask.get(wordIndex) & bitMask) != 0) return i; + if ((mask.get(wordIndex) & bitMask) != 0) { + return i; + } } return -1; } + // ======================== События обновления ======================== + public static void onBlockLightUpdated(long sectionPos) { int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos); int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos); @@ -124,7 +132,7 @@ public final class FogLightStreamer { int index = sectionToLocalIndex(sectionX, sectionY, sectionZ); if (index >= 0) { - preparedBlockData.remove(index); + clearBit(readyBlockMask, index); setBit(pendingBlockMask, index); } } @@ -136,11 +144,13 @@ public final class FogLightStreamer { int index = sectionToLocalIndex(sectionX, sectionY, sectionZ); if (index >= 0) { - preparedSkyData.remove(index); + clearBit(readySkyMask, index); setBit(pendingSkyMask, index); } } + // ======================== Окно ======================== + public static void update( int oldOriginChunkX, int oldOriginChunkZ, int newOriginChunkX, int newOriginChunkZ, @@ -194,10 +204,13 @@ public final class FogLightStreamer { } 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++) { + int wx = worldX + i; for (int j = 0; j < countZ; j++) { + int wz = worldZ + j; 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) { setBit(initialBlockMask, index); setBit(initialSkyMask, index); @@ -216,38 +229,34 @@ public final class FogLightStreamer { FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount); - preparedBlockData.clear(); - preparedSkyData.clear(); - - for (int i = 0; i < pendingBlockMask.length(); i++) { + for (int i = 0; i < TOTAL_WORDS; i++) { + pendingBlockMask.set(i, 0L); + pendingSkyMask.set(i, 0L); + readyBlockMask.set(i, 0L); + readySkyMask.set(i, 0L); initialBlockMask.set(i, -1L); initialSkyMask.set(i, -1L); } - int totalWords = (TOTAL_SECTIONS + 63) / 64; + int extraBits = TOTAL_SECTIONS & 63; if (extraBits != 0) { long mask = (1L << extraBits) - 1; - initialBlockMask.set(totalWords - 1, mask); - initialSkyMask.set(totalWords - 1, mask); + initialBlockMask.set(TOTAL_WORDS - 1, mask); + initialSkyMask.set(TOTAL_WORDS - 1, mask); } } private static void collectRegions(int worldX, int worldZ, int countX, int countZ) { - int[] xStarts = new int[2]; - int[] xCounts = new int[2]; - 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); + int xSegCount = splitIntoSegments(worldX, countX, FogLightVolume.CHUNKS_X, tempXStarts, tempXCounts); + int zSegCount = splitIntoSegments(worldZ, countZ, FogLightVolume.CHUNKS_Z, tempZStarts, tempZCounts); for (int i = 0; i < xSegCount; i++) { for (int j = 0; j < zSegCount; j++) { if (regionCount >= MAX_REGIONS) return; - regionPhysX[regionCount] = xStarts[i]; - regionPhysZ[regionCount] = zStarts[j]; - regionWidth[regionCount] = xCounts[i]; - regionDepth[regionCount] = zCounts[j]; + regionPhysX[regionCount] = tempXStarts[i]; + regionPhysZ[regionCount] = tempZStarts[j]; + regionWidth[regionCount] = tempXCounts[i]; + regionDepth[regionCount] = tempZCounts[j]; regionCount++; } } @@ -255,8 +264,8 @@ public final class FogLightStreamer { private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) { if (count == 0) return 0; - int first = Math.floorMod(start, dim); - int last = Math.floorMod(start + count - 1, dim); + int first = floorMod(start, dim); + int last = floorMod(start + count - 1, dim); if (first <= last) { outStarts[0] = first; 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; - while (processed < MAX_INITIAL_SECTIONS_PER_TICK) { + while (processedBlock < MAX_INITIAL_SECTIONS_PER_TICK) { index = nextSetBit(initialBlockMask, index); if (index < 0) break; if (prepareBlockLight(index)) { clearBit(initialBlockMask, index); - processed++; + processedBlock++; } index++; } - // Initial load - sky light index = 0; - while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2) { - 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) { + while (processedBlock < MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK) { index = nextSetBit(pendingBlockMask, index); if (index < 0) break; if (prepareBlockLight(index)) { clearBit(pendingBlockMask, index); - processed++; + processedBlock++; } index++; } - // Incremental - sky light 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); if (index < 0) break; if (prepareSkyLight(index)) { clearBit(pendingSkyMask, index); - processed++; + processedSky++; } index++; } @@ -327,22 +341,20 @@ public final class FogLightStreamer { int processed = 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); if (index < 0) break; if (uploadBlockLight(index)) { - clearBit(readyBlockMask, index); processed++; } index++; } 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); if (index < 0) break; if (uploadSkyLight(index)) { - clearBit(readySkyMask, index); processed++; } index++; @@ -350,21 +362,22 @@ public final class FogLightStreamer { } private static boolean prepareBlockLight(int index) { - // Извлекаем физические координаты int physicalX = index % FogLightVolume.CHUNKS_X; int physicalZ = (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 sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); + int sectionX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4); - byte[] data = FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ); - if (data == null) return false; + if (!FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ, index)) { + return false; + } + + preparedBlockWorldX[index] = sectionX; + preparedBlockWorldY[index] = sectionY; + preparedBlockWorldZ[index] = sectionZ; - // Сохраняем данные вместе с мировыми координатами - preparedBlockData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ)); setBit(readyBlockMask, index); return true; } @@ -374,14 +387,18 @@ public final class FogLightStreamer { int physicalZ = (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 sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); + int sectionX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4); - byte[] data = FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ); - if (data == null) return false; + if (!FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ, index)) { + return false; + } + + preparedSkyWorldX[index] = sectionX; + preparedSkyWorldY[index] = sectionY; + preparedSkyWorldZ[index] = sectionZ; - preparedSkyData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ)); setBit(readySkyMask, index); return true; } @@ -391,27 +408,21 @@ public final class FogLightStreamer { int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z); - PreparedSection prepared = preparedBlockData.remove(index); - if (prepared == null) { - 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 currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4); - // Проверяем, что мировые координаты не изменились - if (prepared.worldSectionX != currentWorldX || - prepared.worldSectionY != currentWorldY || - prepared.worldSectionZ != currentWorldZ) { - // Координаты изменились — отбрасываем данные и помечаем как pending + if (preparedBlockWorldX[index] != currentWorldX || + preparedBlockWorldY[index] != currentWorldY || + preparedBlockWorldZ[index] != currentWorldZ) { + clearBit(readyBlockMask, index); setBit(pendingBlockMask, index); return false; } - FogLightVolume.uploadBlockLightSection(physicalX, localSectionY, physicalZ, prepared.data); + FogLightVolume.uploadBlockLightSection(physicalX, localSectionY, physicalZ, + FogLightProvider.getBlockLightData(index)); + clearBit(readyBlockMask, index); return true; } @@ -420,24 +431,21 @@ public final class FogLightStreamer { int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z); - PreparedSection prepared = preparedSkyData.remove(index); - if (prepared == null) { - 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 currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4); - if (prepared.worldSectionX != currentWorldX || - prepared.worldSectionY != currentWorldY || - prepared.worldSectionZ != currentWorldZ) { + if (preparedSkyWorldX[index] != currentWorldX || + preparedSkyWorldY[index] != currentWorldY || + preparedSkyWorldZ[index] != currentWorldZ) { + clearBit(readySkyMask, index); setBit(pendingSkyMask, index); return false; } - FogLightVolume.uploadSkyLightSection(physicalX, localSectionY, physicalZ, prepared.data); + FogLightVolume.uploadSkyLightSection(physicalX, localSectionY, physicalZ, + FogLightProvider.getSkyLightData(index)); + clearBit(readySkyMask, index); return true; } } \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java index 4609c9f..e0e70f5 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java @@ -46,6 +46,7 @@ public final class FogSystem { private static void initialize() { FogWorldVolume.init(); FogLightVolume.init(); + FogLightStreamer.init(); FogSimulationManager.init(); initialized = true; } diff --git a/src/client/resources/assets/veila/shaders/fog_raymarching.frag b/src/client/resources/assets/veila/shaders/fog_raymarching.frag index f491d9d..771fc4c 100644 --- a/src/client/resources/assets/veila/shaders/fog_raymarching.frag +++ b/src/client/resources/assets/veila/shaders/fog_raymarching.frag @@ -99,7 +99,7 @@ float density(vec3 worldPos) { vec3 uv = worldToUV(worldPos); 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)