From ad2e5adcde5e103e66e69cb31bbfa749f70f6b1f Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Sun, 2 Aug 2026 04:39:52 +0400 Subject: [PATCH] =?UTF-8?q?PBO=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA=D1=83=20?= =?UTF-8?q?=D1=87=D0=B0=D0=BD=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/render/fog/FogChunkStreamer.java | 196 +++++++++++------- .../client/render/fog/FogDensityTextures.java | 102 +++++---- .../client/render/fog/FogWorldVolume.java | 75 ++++++- 3 files changed, 258 insertions(+), 115 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 cfe68dd..5134bf3 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 @@ -11,7 +11,6 @@ public final class FogChunkStreamer { LOADED } - // Очередь изменений блоков (потокобезопасная) private static final ConcurrentLinkedQueue blockChangeQueue = new ConcurrentLinkedQueue<>(); @@ -39,6 +38,13 @@ public final class FogChunkStreamer { private static boolean firstUpdate = true; + private static final int MAX_REGIONS = 16; + private static final int[] regionPhysX = new int[MAX_REGIONS]; + private static final int[] regionPhysZ = new int[MAX_REGIONS]; + private static final int[] regionWidth = new int[MAX_REGIONS]; + private static final int[] regionDepth = new int[MAX_REGIONS]; + private static int regionCount = 0; + private FogChunkStreamer() { } @@ -51,13 +57,9 @@ public final class FogChunkStreamer { BlockState newState ) {} - // Вызывается из любого потока (в том числе серверного) public static void onBlockChanged( - int chunkX, - int chunkZ, - int blockX, - int blockY, - int blockZ, + int chunkX, int chunkZ, + int blockX, int blockY, int blockZ, BlockState newState ) { blockChangeQueue.add(new BlockChangeEvent(chunkX, chunkZ, blockX, blockY, blockZ, newState)); @@ -97,48 +99,124 @@ public final class FogChunkStreamer { return; } + regionCount = 0; + if (dx > 0) { - for (int i = 0; i < dx; i++) { - int worldChunkX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx + i; - for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) { - markSlotPending(worldChunkX, newOriginChunkZ + z); - } - } + int worldX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx; + int worldZ = newOriginChunkZ; + collectRegions(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z); + markPendingRange(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z); } if (dx < 0) { - for (int i = 0; i < -dx; i++) { - int worldChunkX = newOriginChunkX + i; - for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) { - markSlotPending(worldChunkX, newOriginChunkZ + z); - } - } + int worldX = newOriginChunkX; + int worldZ = newOriginChunkZ; + collectRegions(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z); + markPendingRange(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z); } if (dz > 0) { - for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) { - int worldChunkX = newOriginChunkX + x; - for (int i = 0; i < dz; i++) { - int worldChunkZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz + i; - markSlotPending(worldChunkX, worldChunkZ); - } - } + int worldX = newOriginChunkX; + int worldZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz; + collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz); + markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz); } if (dz < 0) { - for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) { - int worldChunkX = newOriginChunkX + x; - for (int i = 0; i < -dz; i++) { - int worldChunkZ = newOriginChunkZ + i; - markSlotPending(worldChunkX, worldChunkZ); - } - } + int worldX = newOriginChunkX; + int worldZ = newOriginChunkZ; + collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz); + markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz); + } + + if (regionCount > 0) { + FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount); + FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount); } uploadReadyData(); } - // Обрабатывает очередь изменений блоков (только в render thread) + 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); + + int[] zStarts = new int[2]; + int[] zCounts = new int[2]; + int zSegCount = splitIntoSegments(worldZ, countZ, FogWorldVolume.CHUNKS_Z, zStarts, zCounts); + + 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]; + regionCount++; + } + } + } + + 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); + + if (first <= last) { + outStarts[0] = first; + outCounts[0] = count; + return 1; + } else { + int firstCount = dim - first; + outStarts[0] = first; + outCounts[0] = firstCount; + outStarts[1] = 0; + outCounts[1] = count - firstCount; + return 2; + } + } + + 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) { @@ -152,40 +230,28 @@ public final class FogChunkStreamer { int ringOffsetX = currentRingChunkOffsetX; int ringOffsetZ = currentRingChunkOffsetZ; - // Проверяем, находится ли чанк в нашей области - int relativeX = event.chunkX - originX; - int relativeZ = event.chunkZ - originZ; + int relativeX = event.chunkX() - originX; + int relativeZ = event.chunkZ() - originZ; if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X || relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) { - return; // Чанк вне области обработки + return; } - // Вычисляем физический индекс 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; + 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; // Y вне диапазона + return; } - // Вычисляем значение для этого вокселя byte value = FogChunkProvider.computePackedVoxelValue(event.newState()); - // Обновляем один воксель в текстуре - FogWorldVolume.updateSingleVoxel( - physicalX, - physicalZ, - localX, - yIndex, - localZ, - value - ); + FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value); } public static void processPending() { @@ -226,28 +292,6 @@ public final class FogChunkStreamer { } } - private static void markAllPending(int originChunkX, int originChunkZ) { - for (int physicalZ = 0; physicalZ < FogWorldVolume.CHUNKS_Z; physicalZ++) { - for (int physicalX = 0; physicalX < FogWorldVolume.CHUNKS_X; physicalX++) { - int worldChunkX = originChunkX + physicalX; - int worldChunkZ = originChunkZ + physicalZ; - markSlotPending(worldChunkX, worldChunkZ); - } - } - } - - private static void markSlotPending(int worldChunkX, int worldChunkZ) { - int physicalX = Math.floorMod(worldChunkX, FogWorldVolume.CHUNKS_X); - int physicalZ = Math.floorMod(worldChunkZ, FogWorldVolume.CHUNKS_Z); - int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX; - - FogWorldVolume.uploadZeros(physicalX, physicalZ); - FogDensityTextures.clearColumn(physicalX, physicalZ); - - statuses[physicalIndex] = ChunkStatus.PENDING; - readyToUpload[physicalIndex] = false; - } - public static void uploadReadyData() { int originX = currentOriginChunkX; int originZ = currentOriginChunkZ; diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java b/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java index ee0e37c..352cede 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java @@ -3,7 +3,9 @@ package su.divan2000.veila.client.render.fog; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL31; import java.nio.ByteBuffer; import java.nio.FloatBuffer; @@ -14,28 +16,42 @@ public final class FogDensityTextures { private static int textureB = -1; private static int readIndex = 0; - // Float буфер с нулями для одного столбца 16×384×16 - private static final FloatBuffer ZEROS_BUFFER = - BufferUtils.createFloatBuffer( - FogWorldVolume.CHUNK_SIZE * - FogWorldVolume.SIZE_Y * - FogWorldVolume.CHUNK_SIZE - ); - - static { - int size = FogWorldVolume.CHUNK_SIZE * FogWorldVolume.SIZE_Y * FogWorldVolume.CHUNK_SIZE; - for (int i = 0; i < size; i++) { - ZEROS_BUFFER.put(0.0f); - } - ZEROS_BUFFER.flip(); - } + // PBO для быстрой очистки регионов (half float нули) + private static int clearPBO = -1; + private static final int MAX_CLEAR_SIZE = FogWorldVolume.SIZE_X * FogWorldVolume.SIZE_Y * FogWorldVolume.SIZE_Z; + private static final int MAX_CLEAR_SIZE_BYTES = MAX_CLEAR_SIZE * 2; // 2 байта на half float private FogDensityTextures() { } + private static void initClearPBO() { + if (clearPBO != -1) return; + + clearPBO = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO); + + // Выделяем буфер в VRAM + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE_BYTES, GL15.GL_STATIC_DRAW); + + // Заполняем нулями (half float 0.0 = 2 нулевых байта: 0x0000) + ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + // Заполняем нулями - это даст half float 0.0 + for (int i = 0; i < MAX_CLEAR_SIZE_BYTES; i++) { + mapped.put((byte) 0); + } + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + } + + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } + public static void init() { if (textureA != -1) return; + // Инициализируем PBO ДО создания текстур + initClearPBO(); + textureA = createDensityTexture(); textureB = createDensityTexture(); } @@ -54,7 +70,7 @@ public final class FogDensityTextures { FogWorldVolume.SIZE_Z, 0, GL11.GL_RED, - GL11.GL_FLOAT, + GL30.GL_HALF_FLOAT, // Исправлено: GL_HALF_FLOAT для 16-bit float (ByteBuffer) null ); @@ -83,39 +99,51 @@ public final class FogDensityTextures { } /** - * Обнуляет столбец (один чанк) в ОБОИХ density текстурах. - * Вызывается при смене окна, чтобы не было фантомного тумана. + * Очищает несколько непрерывных физических регионов в обеих density текстурах через PBO. */ - public static void clearColumn(int textureChunkX, int textureChunkZ) { - clearColumnInTexture(textureA, textureChunkX, textureChunkZ); - clearColumnInTexture(textureB, textureChunkX, textureChunkZ); + public static void clearRegions( + int[] physX, int[] physZ, + int[] widthChunks, int[] depthChunks, + int count + ) { + clearRegionsInTexture(textureA, physX, physZ, widthChunks, depthChunks, count); + clearRegionsInTexture(textureB, physX, physZ, widthChunks, depthChunks, count); } - private static void clearColumnInTexture(int texture, int textureChunkX, int textureChunkZ) { + private static void clearRegionsInTexture( + int texture, + int[] physX, int[] physZ, + int[] widthChunks, int[] depthChunks, + int count + ) { GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO); GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0); GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0); - // Сбрасываем position буфера - ZEROS_BUFFER.rewind(); + for (int i = 0; i < count; i++) { + int w = widthChunks[i] * FogWorldVolume.CHUNK_SIZE; + int h = FogWorldVolume.SIZE_Y; + int d = depthChunks[i] * FogWorldVolume.CHUNK_SIZE; - GL30.glTexSubImage3D( - GL12.GL_TEXTURE_3D, - 0, - textureChunkX * FogWorldVolume.CHUNK_SIZE, - 0, - textureChunkZ * FogWorldVolume.CHUNK_SIZE, - FogWorldVolume.CHUNK_SIZE, - FogWorldVolume.SIZE_Y, - FogWorldVolume.CHUNK_SIZE, - GL11.GL_RED, - GL11.GL_FLOAT, - ZEROS_BUFFER - ); + // 0L = offset 0 внутри PBO (начало буфера) + GL12.glTexSubImage3D( + GL12.GL_TEXTURE_3D, + 0, + physX[i] * FogWorldVolume.CHUNK_SIZE, + 0, + physZ[i] * FogWorldVolume.CHUNK_SIZE, + w, h, d, + GL11.GL_RED, + GL30.GL_HALF_FLOAT, + 0L // ← long offset вместо ByteBuffer + ); + } + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } } \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogWorldVolume.java b/src/client/java/su/divan2000/veila/client/render/fog/FogWorldVolume.java index a379bc1..a2a98dd 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogWorldVolume.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogWorldVolume.java @@ -3,7 +3,9 @@ package su.divan2000.veila.client.render.fog; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL31; import java.nio.ByteBuffer; @@ -23,11 +25,12 @@ public final class FogWorldVolume { private static int texture = -1; + private static final byte AIR_VALUE = new FogBlockProperties(false, 0, 0).pack(); + private static final byte[] ZEROS_ARRAY; static { ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE]; - byte airValue = new FogBlockProperties(false, 0, 0).pack(); - java.util.Arrays.fill(ZEROS_ARRAY, airValue); + java.util.Arrays.fill(ZEROS_ARRAY, AIR_VALUE); } private static final ByteBuffer CHUNK_BUFFER = @@ -36,12 +39,40 @@ public final class FogWorldVolume { private static final ByteBuffer SINGLE_VOXEL_BUFFER = BufferUtils.createByteBuffer(1); + // PBO для быстрой очистки регионов + private static int clearPBO = -1; + private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z; + private FogWorldVolume() { } + private static void initClearPBO() { + if (clearPBO != -1) return; + + clearPBO = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO); + + // Выделяем буфер в VRAM + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW); + + // Заполняем AIR_VALUE через mapping + ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + for (int i = 0; i < MAX_CLEAR_SIZE; i++) { + mapped.put(AIR_VALUE); + } + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + } + + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } + public static void init() { if (texture != -1) return; + // Инициализируем PBO ДО создания текстуры + initClearPBO(); + texture = GL11.glGenTextures(); GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); @@ -173,4 +204,44 @@ public final class FogWorldVolume { public static int getTexture() { return texture; } + + /** + * Очищает несколько непрерывных физических регионов через PBO (асинхронно). + * Размеры widthChunks и depthChunks задаются в чанках (не в блоках!). + */ + public static void clearRegions( + int[] physX, int[] physZ, + int[] widthChunks, int[] depthChunks, + int count + ) { + GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO); + + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); + GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0); + GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0); + + for (int i = 0; i < count; i++) { + int w = widthChunks[i] * CHUNK_SIZE; + int h = SIZE_Y; + int d = depthChunks[i] * CHUNK_SIZE; + + // 0L = offset 0 внутри PBO (начало буфера) + GL12.glTexSubImage3D( + GL12.GL_TEXTURE_3D, + 0, + physX[i] * CHUNK_SIZE, + 0, + physZ[i] * CHUNK_SIZE, + w, h, d, + GL30.GL_RED_INTEGER, + GL11.GL_UNSIGNED_BYTE, + 0L // ← long offset вместо ByteBuffer + ); + } + + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + } } \ No newline at end of file