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 19621ec..585770b 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 @@ -17,6 +17,8 @@ public final class FogChunkStreamer { FogWorldVolume.CHUNK_SIZE; private static final int MAX_CHUNKS_PER_TICK = 4; + private static final int MAX_READY_CHUNK_UPLOADS_PER_TICK = 4; + private static volatile int burstFrames = 0; // Битовые маски вместо массивов статусов private static final AtomicLongArray pendingMask = new AtomicLongArray(TOTAL_WORDS); @@ -143,14 +145,12 @@ public final class FogChunkStreamer { int dz = newOriginChunkZ - oldOriginChunkZ; if (dx == 0 && dz == 0) { - uploadReadyData(); return; } if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X || Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) { markAllPending(); - uploadReadyData(); return; } @@ -313,7 +313,9 @@ public final class FogChunkStreamer { int processedCount = 0; int index = 0; - while (processedCount < MAX_CHUNKS_PER_TICK) { + int limit = MAX_CHUNKS_PER_TICK + (burstFrames > 0 ? MAX_CHUNKS_PER_TICK : 0); + + while (processedCount < limit) { index = nextSetBit(pendingMask, index); if (index < 0) break; @@ -332,6 +334,7 @@ public final class FogChunkStreamer { } index++; } + if (burstFrames > 0) burstFrames--; } public static void uploadReadyData() { @@ -340,8 +343,13 @@ public final class FogChunkStreamer { int ringOffsetX = currentRingChunkOffsetX; int ringOffsetZ = currentRingChunkOffsetZ; + int processed = 0; int index = 0; - while (true) { + + int limit = MAX_READY_CHUNK_UPLOADS_PER_TICK + (burstFrames > 0 ? MAX_READY_CHUNK_UPLOADS_PER_TICK : 0); + + FogWorldVolume.beginUpload(); + while (processed < limit) { index = nextSetBit(readyMask, index); if (index < 0) break; @@ -354,8 +362,9 @@ public final class FogChunkStreamer { if (preparedWorldChunkX[index] == currentWorldX && preparedWorldChunkZ[index] == currentWorldZ) { - FogWorldVolume.uploadChunkFromArray(physicalX, physicalZ, preparedData[index]); + FogWorldVolume.uploadChunkFromArrayNoBind(physicalX, physicalZ, preparedData[index]); clearBit(readyMask, index); + processed++; } else { // Координаты изменились — помечаем как pending заново clearBit(readyMask, index); @@ -363,5 +372,11 @@ public final class FogChunkStreamer { } index++; } + FogWorldVolume.endUpload(); + if (burstFrames > 0) burstFrames--; + } + + public static void setBurst(int frames) { + burstFrames = Math.max(burstFrames, frames); } } \ 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 b1e486c..076bc95 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 @@ -29,8 +29,11 @@ public final class FogLightStreamer { private static final int[] preparedSkyWorldZ = new int[TOTAL_SECTIONS]; // Более равномерное распределение: меньше 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 final int MAX_INITIAL_SECTIONS_PER_TICK = 16; + private static final int MAX_PENDING_SECTIONS_PER_TICK = 16; + private static final int MAX_READY_BLOCK_SECTIONS_PER_TICK = 8; + private static final int MAX_READY_SKY_SECTIONS_PER_TICK = 8; + private static volatile int burstFrames = 0; private static volatile int currentOriginChunkX; private static volatile int currentOriginChunkZ; @@ -293,7 +296,8 @@ public final class FogLightStreamer { // Чередование: немного initial block, немного pending block, // немного initial sky, немного pending sky int index = 0; - while (processedBlock < MAX_INITIAL_SECTIONS_PER_TICK) { + int initialLimit = MAX_INITIAL_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_INITIAL_SECTIONS_PER_TICK : 0); + while (processedBlock < initialLimit) { index = nextSetBit(initialBlockMask, index); if (index < 0) break; if (prepareBlockLight(index)) { @@ -304,7 +308,8 @@ public final class FogLightStreamer { } index = 0; - while (processedBlock < MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK) { + int pendingLimit = MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_PENDING_SECTIONS_PER_TICK : 0); + while (processedBlock < pendingLimit) { index = nextSetBit(pendingBlockMask, index); if (index < 0) break; if (prepareBlockLight(index)) { @@ -315,7 +320,8 @@ public final class FogLightStreamer { } index = 0; - while (processedSky < MAX_INITIAL_SECTIONS_PER_TICK) { + int initialSkyLimit = MAX_INITIAL_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_INITIAL_SECTIONS_PER_TICK : 0); + while (processedSky < initialSkyLimit) { index = nextSetBit(initialSkyMask, index); if (index < 0) break; if (prepareSkyLight(index)) { @@ -326,7 +332,8 @@ public final class FogLightStreamer { } index = 0; - while (processedSky < MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK) { + int pendingSkyLimit = MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_PENDING_SECTIONS_PER_TICK : 0); + while (processedSky < pendingSkyLimit) { index = nextSetBit(pendingSkyMask, index); if (index < 0) break; if (prepareSkyLight(index)) { @@ -335,30 +342,42 @@ public final class FogLightStreamer { } index++; } + if (burstFrames > 0) burstFrames--; } public static void uploadReadySections() { - int processed = 0; + int processedBlock = 0; + int processedSky = 0; int index = 0; - - while (processed < MAX_PENDING_SECTIONS_PER_TICK) { + FogLightVolume.beginUpload(FogLightVolume.getBlockLightTexture()); + int blockLimit = MAX_READY_BLOCK_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_READY_BLOCK_SECTIONS_PER_TICK : 0); + while (processedBlock < blockLimit) { index = nextSetBit(readyBlockMask, index); if (index < 0) break; if (uploadBlockLight(index)) { - processed++; + processedBlock++; } index++; } + FogLightVolume.endUpload(); index = 0; - while (processed < MAX_PENDING_SECTIONS_PER_TICK * 2) { + FogLightVolume.beginUpload(FogLightVolume.getSkyLightTexture()); + int skyLimit = MAX_READY_SKY_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_READY_SKY_SECTIONS_PER_TICK : 0); + while (processedSky < skyLimit) { index = nextSetBit(readySkyMask, index); if (index < 0) break; if (uploadSkyLight(index)) { - processed++; + processedSky++; } index++; } + FogLightVolume.endUpload(); + if (burstFrames > 0) burstFrames--; + } + + public static void setBurst(int frames) { + burstFrames = Math.max(burstFrames, frames); } private static boolean prepareBlockLight(int index) { @@ -420,7 +439,7 @@ public final class FogLightStreamer { return false; } - FogLightVolume.uploadBlockLightSection(physicalX, localSectionY, physicalZ, + FogLightVolume.uploadBlockLightSectionNoBind(physicalX, localSectionY, physicalZ, FogLightProvider.getBlockLightData(index)); clearBit(readyBlockMask, index); return true; @@ -443,7 +462,7 @@ public final class FogLightStreamer { return false; } - FogLightVolume.uploadSkyLightSection(physicalX, localSectionY, physicalZ, + FogLightVolume.uploadSkyLightSectionNoBind(physicalX, localSectionY, physicalZ, FogLightProvider.getSkyLightData(index)); clearBit(readySkyMask, index); return true; diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java b/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java index 86cc085..4e1f0d8 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java @@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL31; +// No GL43 usage: keep compatibility with OpenGL 3.2 import java.nio.ByteBuffer; @@ -43,6 +44,8 @@ public final class FogLightVolume { // PBO для быстрой очистки private static int clearBlockPBO = -1; private static int clearSkyPBO = -1; + // PBO для загрузки секций + private static int uploadPBO = -1; // Буферы для загрузки секций private static final ByteBuffer BLOCK_SECTION_UPLOAD_BUFFER = @@ -92,6 +95,7 @@ public final class FogLightVolume { if (blockLightTexture != -1) return; initClearPBOs(); + initUploadPBO(); // Block light texture blockLightTexture = GL11.glGenTextures(); @@ -177,20 +181,14 @@ public final class FogLightVolume { BLOCK_SECTION_UPLOAD_BUFFER.put(data); BLOCK_SECTION_UPLOAD_BUFFER.flip(); - GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture); - 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); - + beginUpload(blockLightTexture); GL30.glTexSubImage3D( GL12.GL_TEXTURE_3D, 0, textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE, 16, 16, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER ); - - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + endUpload(); } public static void uploadSkyLightSection( @@ -200,19 +198,91 @@ public final class FogLightVolume { SKY_SECTION_UPLOAD_BUFFER.put(data); SKY_SECTION_UPLOAD_BUFFER.flip(); - GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture); - 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); - + beginUpload(skyLightTexture); GL30.glTexSubImage3D( GL12.GL_TEXTURE_3D, 0, textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE, 16, 16, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER ); + endUpload(); + } + public static void uploadBlockLightSectionNoBind( + int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data + ) { + // Try to upload via PBO to avoid CPU->GPU stalls + if (uploadPBO != -1) { + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, BLOCK_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW); + ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + mapped.put(data); + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, 0, + textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE, + 16, 16, 16, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L + ); + } + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } else { + BLOCK_SECTION_UPLOAD_BUFFER.clear(); + BLOCK_SECTION_UPLOAD_BUFFER.put(data); + BLOCK_SECTION_UPLOAD_BUFFER.flip(); + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, 0, + textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE, + 16, 16, 16, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER + ); + } + } + + public static void uploadSkyLightSectionNoBind( + int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data + ) { + if (uploadPBO != -1) { + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, SKY_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW); + ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + mapped.put(data); + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, 0, + textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE, + 16, 16, 16, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L + ); + } + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } else { + SKY_SECTION_UPLOAD_BUFFER.clear(); + SKY_SECTION_UPLOAD_BUFFER.put(data); + SKY_SECTION_UPLOAD_BUFFER.flip(); + + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, 0, + textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE, + 16, 16, 16, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER + ); + } + } + + public static void beginUpload(int texture) { + GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); + 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); + } + + public static void endUpload() { GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } @@ -229,6 +299,7 @@ public final class FogLightVolume { int[] widthChunks, int[] depthChunks, int count ) { + // Use PBO-based clears for OpenGL 3.2 compatibility GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0); @@ -268,4 +339,12 @@ public final class FogLightVolume { GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } + + private static void initUploadPBO() { + if (uploadPBO != -1) return; + uploadPBO = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, BLOCK_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } } \ 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 e0e70f5..61fc96b 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 @@ -103,6 +103,9 @@ public final class FogSystem { ringChunkOffsetX, ringChunkOffsetZ ); + // Boost uploads for a few frames to catch up after a window shift + FogChunkStreamer.setBurst(8); + FogLightStreamer.setBurst(8); } public static int originChunkX() { 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 a2a98dd..75c8cbd 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 @@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL31; +// No GL43 usage: keep compatibility with OpenGL 3.2 import java.nio.ByteBuffer; @@ -41,6 +42,8 @@ public final class FogWorldVolume { // PBO для быстрой очистки регионов private static int clearPBO = -1; + // PBO для загрузки чанков + private static int uploadPBO = -1; private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z; private FogWorldVolume() { @@ -72,6 +75,7 @@ public final class FogWorldVolume { // Инициализируем PBO ДО создания текстуры initClearPBO(); + initUploadPBO(); texture = GL11.glGenTextures(); @@ -196,6 +200,76 @@ public final class FogWorldVolume { uploadChunk(textureChunkX, textureChunkZ, buffer); } + public static void beginUpload() { + GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); + 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); + } + + public static void uploadChunkFromArrayNoBind( + int textureChunkX, + int textureChunkZ, + byte[] data + ) { + // Use PBO to upload without stalling the driver + if (uploadPBO != -1) { + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO); + // Orphan and allocate + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_SIZE * SIZE_Y * CHUNK_SIZE, GL15.GL_STREAM_DRAW); + ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + mapped.put(data); + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, + 0, + textureChunkX * CHUNK_SIZE, + 0, + textureChunkZ * CHUNK_SIZE, + CHUNK_SIZE, + SIZE_Y, + CHUNK_SIZE, + GL30.GL_RED_INTEGER, + GL11.GL_UNSIGNED_BYTE, + 0L + ); + } + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } else { + ByteBuffer buffer = chunkBuffer(); + buffer.put(data); + buffer.flip(); + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, + 0, + textureChunkX * CHUNK_SIZE, + 0, + textureChunkZ * CHUNK_SIZE, + CHUNK_SIZE, + SIZE_Y, + CHUNK_SIZE, + GL30.GL_RED_INTEGER, + GL11.GL_UNSIGNED_BYTE, + buffer + ); + } + } + + public static void endUpload() { + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + } + + private static void initUploadPBO() { + if (uploadPBO != -1) return; + uploadPBO = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_SIZE * SIZE_Y * CHUNK_SIZE, GL15.GL_STREAM_DRAW); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } + public static ByteBuffer chunkBuffer() { CHUNK_BUFFER.clear(); return CHUNK_BUFFER; @@ -214,6 +288,7 @@ public final class FogWorldVolume { int[] widthChunks, int[] depthChunks, int count ) { + // Use PBO-based clears for OpenGL 3.2 compatibility GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);