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
}
// Очередь изменений блоков (потокобезопасная)
private static final ConcurrentLinkedQueue<BlockChangeEvent> 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;
@@ -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);
}
}
@@ -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);
}
}