PBO ускоряет очистку чанков

This commit is contained in:
2026-08-02 04:39:52 +04:00
parent efc345fa0b
commit ad2e5adcde
3 changed files with 258 additions and 115 deletions
@@ -11,7 +11,6 @@ public final class FogChunkStreamer {
LOADED LOADED
} }
// Очередь изменений блоков (потокобезопасная)
private static final ConcurrentLinkedQueue<BlockChangeEvent> blockChangeQueue = private static final ConcurrentLinkedQueue<BlockChangeEvent> blockChangeQueue =
new ConcurrentLinkedQueue<>(); new ConcurrentLinkedQueue<>();
@@ -39,6 +38,13 @@ public final class FogChunkStreamer {
private static boolean firstUpdate = true; 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() { private FogChunkStreamer() {
} }
@@ -51,13 +57,9 @@ public final class FogChunkStreamer {
BlockState newState BlockState newState
) {} ) {}
// Вызывается из любого потока (в том числе серверного)
public static void onBlockChanged( public static void onBlockChanged(
int chunkX, int chunkX, int chunkZ,
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)); blockChangeQueue.add(new BlockChangeEvent(chunkX, chunkZ, blockX, blockY, blockZ, newState));
@@ -97,48 +99,124 @@ public final class FogChunkStreamer {
return; return;
} }
regionCount = 0;
if (dx > 0) { if (dx > 0) {
for (int i = 0; i < dx; i++) { int worldX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx;
int worldChunkX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx + i; int worldZ = newOriginChunkZ;
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) { collectRegions(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z);
markSlotPending(worldChunkX, newOriginChunkZ + z); markPendingRange(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z);
}
}
} }
if (dx < 0) { if (dx < 0) {
for (int i = 0; i < -dx; i++) { int worldX = newOriginChunkX;
int worldChunkX = newOriginChunkX + i; int worldZ = newOriginChunkZ;
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) { collectRegions(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z);
markSlotPending(worldChunkX, newOriginChunkZ + z); markPendingRange(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z);
}
}
} }
if (dz > 0) { if (dz > 0) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) { int worldX = newOriginChunkX;
int worldChunkX = newOriginChunkX + x; int worldZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz;
for (int i = 0; i < dz; i++) { collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz);
int worldChunkZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz + i; markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz);
markSlotPending(worldChunkX, worldChunkZ);
}
}
} }
if (dz < 0) { if (dz < 0) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) { int worldX = newOriginChunkX;
int worldChunkX = newOriginChunkX + x; int worldZ = newOriginChunkZ;
for (int i = 0; i < -dz; i++) { collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz);
int worldChunkZ = newOriginChunkZ + i; markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz);
markSlotPending(worldChunkX, worldChunkZ);
}
} }
if (regionCount > 0) {
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
} }
uploadReadyData(); 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() { public static void processBlockChanges() {
BlockChangeEvent event; BlockChangeEvent event;
while ((event = blockChangeQueue.poll()) != null) { while ((event = blockChangeQueue.poll()) != null) {
@@ -152,40 +230,28 @@ public final class FogChunkStreamer {
int ringOffsetX = currentRingChunkOffsetX; int ringOffsetX = currentRingChunkOffsetX;
int ringOffsetZ = currentRingChunkOffsetZ; int ringOffsetZ = currentRingChunkOffsetZ;
// Проверяем, находится ли чанк в нашей области int relativeX = event.chunkX() - originX;
int relativeX = event.chunkX - originX; int relativeZ = event.chunkZ() - originZ;
int relativeZ = event.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; // Чанк вне области обработки return;
} }
// Вычисляем физический индекс
int physicalX = Math.floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X); int physicalX = Math.floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
int physicalZ = Math.floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z); int physicalZ = Math.floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
// Вычисляем позицию вокселя int localX = event.blockX() & 15;
int localX = event.blockX & 15; int localZ = event.blockZ() & 15;
int localZ = event.blockZ & 15; int yIndex = event.blockY() - FogWorldVolume.MIN_Y;
int yIndex = event.blockY - FogWorldVolume.MIN_Y;
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) { if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
return; // Y вне диапазона return;
} }
// Вычисляем значение для этого вокселя
byte value = FogChunkProvider.computePackedVoxelValue(event.newState()); 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() { 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() { public static void uploadReadyData() {
int originX = currentOriginChunkX; int originX = currentOriginChunkX;
int originZ = currentOriginChunkZ; int originZ = currentOriginChunkZ;
@@ -3,7 +3,9 @@ package su.divan2000.veila.client.render.fog;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
@@ -14,28 +16,42 @@ public final class FogDensityTextures {
private static int textureB = -1; private static int textureB = -1;
private static int readIndex = 0; private static int readIndex = 0;
// Float буфер с нулями для одного столбца 16×384×16 // PBO для быстрой очистки регионов (half float нули)
private static final FloatBuffer ZEROS_BUFFER = private static int clearPBO = -1;
BufferUtils.createFloatBuffer( private static final int MAX_CLEAR_SIZE = FogWorldVolume.SIZE_X * FogWorldVolume.SIZE_Y * FogWorldVolume.SIZE_Z;
FogWorldVolume.CHUNK_SIZE * private static final int MAX_CLEAR_SIZE_BYTES = MAX_CLEAR_SIZE * 2; // 2 байта на half float
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();
}
private FogDensityTextures() { 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() { public static void init() {
if (textureA != -1) return; if (textureA != -1) return;
// Инициализируем PBO ДО создания текстур
initClearPBO();
textureA = createDensityTexture(); textureA = createDensityTexture();
textureB = createDensityTexture(); textureB = createDensityTexture();
} }
@@ -54,7 +70,7 @@ public final class FogDensityTextures {
FogWorldVolume.SIZE_Z, FogWorldVolume.SIZE_Z,
0, 0,
GL11.GL_RED, GL11.GL_RED,
GL11.GL_FLOAT, GL30.GL_HALF_FLOAT, // Исправлено: GL_HALF_FLOAT для 16-bit float
(ByteBuffer) null (ByteBuffer) null
); );
@@ -83,39 +99,51 @@ public final class FogDensityTextures {
} }
/** /**
* Обнуляет столбец (один чанк) в ОБОИХ density текстурах. * Очищает несколько непрерывных физических регионов в обеих density текстурах через PBO.
* Вызывается при смене окна, чтобы не было фантомного тумана.
*/ */
public static void clearColumn(int textureChunkX, int textureChunkZ) { public static void clearRegions(
clearColumnInTexture(textureA, textureChunkX, textureChunkZ); int[] physX, int[] physZ,
clearColumnInTexture(textureB, textureChunkX, textureChunkZ); 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); 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_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0); GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0); GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
// Сбрасываем position буфера for (int i = 0; i < count; i++) {
ZEROS_BUFFER.rewind(); int w = widthChunks[i] * FogWorldVolume.CHUNK_SIZE;
int h = FogWorldVolume.SIZE_Y;
int d = depthChunks[i] * FogWorldVolume.CHUNK_SIZE;
GL30.glTexSubImage3D( // 0L = offset 0 внутри PBO (начало буфера)
GL12.glTexSubImage3D(
GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_3D,
0, 0,
textureChunkX * FogWorldVolume.CHUNK_SIZE, physX[i] * FogWorldVolume.CHUNK_SIZE,
0, 0,
textureChunkZ * FogWorldVolume.CHUNK_SIZE, physZ[i] * FogWorldVolume.CHUNK_SIZE,
FogWorldVolume.CHUNK_SIZE, w, h, d,
FogWorldVolume.SIZE_Y,
FogWorldVolume.CHUNK_SIZE,
GL11.GL_RED, GL11.GL_RED,
GL11.GL_FLOAT, GL30.GL_HALF_FLOAT,
ZEROS_BUFFER 0L // ← long offset вместо ByteBuffer
); );
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
} }
} }
@@ -3,7 +3,9 @@ package su.divan2000.veila.client.render.fog;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -23,11 +25,12 @@ public final class FogWorldVolume {
private static int texture = -1; private static int texture = -1;
private static final byte AIR_VALUE = new FogBlockProperties(false, 0, 0).pack();
private static final byte[] ZEROS_ARRAY; private static final byte[] ZEROS_ARRAY;
static { static {
ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE]; ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
byte airValue = new FogBlockProperties(false, 0, 0).pack(); java.util.Arrays.fill(ZEROS_ARRAY, AIR_VALUE);
java.util.Arrays.fill(ZEROS_ARRAY, airValue);
} }
private static final ByteBuffer CHUNK_BUFFER = private static final ByteBuffer CHUNK_BUFFER =
@@ -36,12 +39,40 @@ public final class FogWorldVolume {
private static final ByteBuffer SINGLE_VOXEL_BUFFER = private static final ByteBuffer SINGLE_VOXEL_BUFFER =
BufferUtils.createByteBuffer(1); 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 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() { public static void init() {
if (texture != -1) return; if (texture != -1) return;
// Инициализируем PBO ДО создания текстуры
initClearPBO();
texture = GL11.glGenTextures(); texture = GL11.glGenTextures();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
@@ -173,4 +204,44 @@ public final class FogWorldVolume {
public static int getTexture() { public static int getTexture() {
return texture; 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);
}
} }