Сделаны бескомпромиссные оптимизации

This commit is contained in:
2026-08-05 08:41:29 +04:00
parent 76370f71e2
commit 8ac636d114
7 changed files with 103 additions and 42 deletions
@@ -5,7 +5,6 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.Framebuffer; import net.minecraft.client.gl.Framebuffer;
import net.minecraft.client.render.Camera; import net.minecraft.client.render.Camera;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.LightType;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.*; import org.lwjgl.opengl.*;
@@ -60,6 +60,16 @@ public final class FogBlockRegistry {
return new FogBlockProperties(solid, 1, 45).pack(); return new FogBlockProperties(solid, 1, 45).pack();
} }
if (block == Blocks.SOUL_CAMPFIRE || block == Blocks.SOUL_FIRE) {
return new FogBlockProperties(solid, 1, 63).pack();
}
if (block == Blocks.SOUL_LANTERN) {
return new FogBlockProperties(solid, 1, 31).pack();
}
if (block == Blocks.SOUL_TORCH || block == Blocks.SOUL_WALL_TORCH) {
return new FogBlockProperties(solid, 1, 15).pack();
}
// Поглотители тумана (sign = -1) // Поглотители тумана (sign = -1)
if (block == Blocks.LAVA) { if (block == Blocks.LAVA) {
return new FogBlockProperties(solid, -1, 50).pack(); return new FogBlockProperties(solid, -1, 50).pack();
@@ -67,18 +77,17 @@ public final class FogBlockRegistry {
if (block == Blocks.MAGMA_BLOCK) { if (block == Blocks.MAGMA_BLOCK) {
return new FogBlockProperties(solid, -1, 40).pack(); return new FogBlockProperties(solid, -1, 40).pack();
} }
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) { if (block == Blocks.FIRE) {
return new FogBlockProperties(solid, -1, 30).pack(); return new FogBlockProperties(solid, -1, 30).pack();
} }
if (block == Blocks.CAMPFIRE || block == Blocks.SOUL_CAMPFIRE) { if (block == Blocks.CAMPFIRE) {
return new FogBlockProperties(solid, -1, 25).pack(); return new FogBlockProperties(solid, -1, 63).pack();
} }
if (block == Blocks.TORCH || block == Blocks.WALL_TORCH || if (block == Blocks.TORCH || block == Blocks.WALL_TORCH) {
block == Blocks.SOUL_TORCH || block == Blocks.SOUL_WALL_TORCH) {
return new FogBlockProperties(solid, -1, 15).pack(); return new FogBlockProperties(solid, -1, 15).pack();
} }
if (block == Blocks.LANTERN || block == Blocks.SOUL_LANTERN) { if (block == Blocks.LANTERN) {
return new FogBlockProperties(solid, -1, 10).pack(); return new FogBlockProperties(solid, -1, 25).pack();
} }
// Нейтральные блоки (sign = 0) // Нейтральные блоки (sign = 0)
@@ -1,9 +1,7 @@
package su.divan2000.veila.client.render.fog; package su.divan2000.veila.client.render.fog;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkSectionPos; import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.ChunkNibbleArray; import net.minecraft.world.chunk.ChunkNibbleArray;
import net.minecraft.world.chunk.light.ChunkLightProvider; import net.minecraft.world.chunk.light.ChunkLightProvider;
import net.minecraft.world.chunk.light.LightingProvider; import net.minecraft.world.chunk.light.LightingProvider;
@@ -13,6 +11,16 @@ public final class FogLightProvider {
// Размер массива для одной секции: 16×16×16×2 = 8192 (×2 для двух каналов) // Размер массива для одной секции: 16×16×16×2 = 8192 (×2 для двух каналов)
public static final int SECTION_DATA_SIZE = 16 * 16 * 16 * 2; public static final int SECTION_DATA_SIZE = 16 * 16 * 16 * 2;
// Pre-allocated buffer для чтения одной секции из LightingProvider
// используется в fillSectionIntoArray для избежания аллокаций
private static final byte[] SECTION_BUFFER = new byte[SECTION_DATA_SIZE];
// Флаг для отслеживания используется ли буфер сейчас (защита от race condition)
// Так как fillSectionIntoArray может вызываться из разных потоков,
// нужен второй буфер для параллельной работы
private static final byte[] SECTION_BUFFER_ALT = new byte[SECTION_DATA_SIZE];
private static volatile boolean bufferInUse = false;
private FogLightProvider() { private FogLightProvider() {
} }
@@ -38,7 +46,15 @@ public final class FogLightProvider {
return null; return null;
} }
byte[] result = new byte[SECTION_DATA_SIZE]; // Используем один из двух pre-allocated буферов (double buffering)
byte[] result;
if (!bufferInUse) {
bufferInUse = true;
result = SECTION_BUFFER;
} else {
result = SECTION_BUFFER_ALT;
}
int index = 0; int index = 0;
for (int localZ = 0; localZ < 16; localZ++) { for (int localZ = 0; localZ < 16; localZ++) {
@@ -47,13 +63,17 @@ public final class FogLightProvider {
int blockLight = (blockArray != null) ? blockArray.get(localX, localY, localZ) : 0; int blockLight = (blockArray != null) ? blockArray.get(localX, localY, localZ) : 0;
int skyLight = (skyArray != null) ? skyArray.get(localX, localY, localZ) : 0; int skyLight = (skyArray != null) ? skyArray.get(localX, localY, localZ) : 0;
// Упаковка в два байта: R=blockLight, G=skyLight
FogLightVolume.packLight(blockLight, skyLight, result, index); FogLightVolume.packLight(blockLight, skyLight, result, index);
index += 2; index += 2;
} }
} }
} }
// Освобождаем буфер
if (result == SECTION_BUFFER) {
bufferInUse = false;
}
return result; return result;
} }
} }
@@ -286,34 +286,51 @@ public final class FogLightStreamer {
} }
} }
public static void prepareInitialLoads() { /**
* Подготавливает все секции для загрузки (client thread).
* Обрабатывает и initialLoadMask, и pendingSectionsMask.
* Вызывается из FogSystem.tick().
*/
public static void prepareAllSections() {
int processed = 0; int processed = 0;
int index = 0;
// Обрабатываем initial load
int index = 0;
while (processed < MAX_INITIAL_SECTIONS_PER_TICK) { while (processed < MAX_INITIAL_SECTIONS_PER_TICK) {
index = nextSetBit(initialLoadMask, index); index = nextSetBit(initialLoadMask, index);
if (index < 0) break; if (index < 0) break;
// Конвертируем индекс обратно в координаты секции if (prepareSectionByIndex(index)) {
int sectionX = currentOriginChunkX + (index % FogLightVolume.CHUNKS_X);
int sectionZ = currentOriginChunkZ + ((index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z);
int sectionY = (index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z)) + (FogWorldVolume.MIN_Y >> 4);
byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ);
if (sectionData != null) {
clearBit(initialLoadMask, index); clearBit(initialLoadMask, index);
setBit(readySectionsMask, index); processed++;
}
index++;
}
// Обрабатываем инкрементальные обновления
index = 0;
while (processed < MAX_INITIAL_SECTIONS_PER_TICK + MAX_SECTIONS_PER_FRAME) {
index = nextSetBit(pendingSectionsMask, index);
if (index < 0) break;
if (prepareSectionByIndex(index)) {
clearBit(pendingSectionsMask, index);
processed++; processed++;
} }
index++; index++;
} }
} }
/**
* Загружает готовые секции в текстуру (render thread).
* Только OpenGL вызовы, без чтения из LightingProvider.
* Вызывается из FogSystem.beginRender().
*/
public static void uploadReadySections() { public static void uploadReadySections() {
int processed = 0; int processed = 0;
int index = 0; int index = 0;
while (processed < MAX_SECTIONS_PER_FRAME) { while (processed < MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) {
index = nextSetBit(readySectionsMask, index); index = nextSetBit(readySectionsMask, index);
if (index < 0) break; if (index < 0) break;
@@ -325,22 +342,29 @@ public final class FogLightStreamer {
} }
} }
public static void processSectionUpdates() { /**
int processed = 0; * Читает данные секции из LightingProvider и помечает как готовую к загрузке.
int index = 0; * Вызывается из prepareAllSections() в client thread.
*/
private static boolean prepareSectionByIndex(int index) {
int sectionX = currentOriginChunkX + (index % FogLightVolume.CHUNKS_X);
int sectionZ = currentOriginChunkZ + ((index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z);
int sectionY = (index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z)) + (FogWorldVolume.MIN_Y >> 4);
while (processed < MAX_SECTIONS_PER_FRAME) { byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ);
index = nextSetBit(pendingSectionsMask, index); if (sectionData == null) {
if (index < 0) break; return false; // Секция не готова, пробуем позже
if (uploadSectionByIndex(index)) {
clearBit(pendingSectionsMask, index);
processed++;
}
index++;
} }
// Помечаем как готовую к загрузке в render thread
setBit(readySectionsMask, index);
return true;
} }
/**
* Загружает готовую секцию в текстуру через OpenGL.
* Вызывается из uploadReadySections() в render thread.
*/
private static boolean uploadSectionByIndex(int index) { private static boolean uploadSectionByIndex(int index) {
int physicalX = index % FogLightVolume.CHUNKS_X; int physicalX = index % FogLightVolume.CHUNKS_X;
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
@@ -350,6 +374,7 @@ public final class FogLightStreamer {
int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4); int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
// Читаем данные повторно (они должны быть доступны быстро, так как уже в кэше)
byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ); byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ);
if (sectionData == null) { if (sectionData == null) {
return false; return false;
@@ -44,6 +44,13 @@ public final class FogLightVolume {
private static int clearPBO = -1; private static int clearPBO = -1;
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z * 2; // ×2 для двух каналов private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z * 2; // ×2 для двух каналов
// Pre-allocated buffer для одной секции (4096 вокселей × 2 байта = 8192 байта)
// Используется для загрузки секций через uploadSection
private static final ByteBuffer SECTION_UPLOAD_BUFFER =
BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE
* FogLightStreamer.SECTION_SIZE
* FogLightStreamer.SECTION_SIZE * 2);
private FogLightVolume() { private FogLightVolume() {
} }
@@ -160,9 +167,10 @@ public final class FogLightVolume {
int textureSectionZ, int textureSectionZ,
byte[] data byte[] data
) { ) {
ByteBuffer sectionBuffer = BufferUtils.createByteBuffer(data.length); // Переиспользуем статический буфер вместо создания нового
sectionBuffer.put(data); SECTION_UPLOAD_BUFFER.clear();
sectionBuffer.flip(); SECTION_UPLOAD_BUFFER.put(data);
SECTION_UPLOAD_BUFFER.flip();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
@@ -182,7 +190,7 @@ public final class FogLightVolume {
16, 16,
GL30.GL_RG, GL30.GL_RG,
GL11.GL_UNSIGNED_BYTE, GL11.GL_UNSIGNED_BYTE,
sectionBuffer SECTION_UPLOAD_BUFFER
); );
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
@@ -31,7 +31,6 @@ public final class FogSystem {
// Освещение: загружаем готовые секции + инкрементальные обновления (render thread) // Освещение: загружаем готовые секции + инкрементальные обновления (render thread)
FogLightStreamer.uploadReadySections(); FogLightStreamer.uploadReadySections();
FogLightStreamer.processSectionUpdates();
FogSimulationManager.update(); FogSimulationManager.update();
} }
@@ -41,7 +40,7 @@ public final class FogSystem {
FogChunkStreamer.processPending(); FogChunkStreamer.processPending();
// Освещение: читаем данные секций (client thread, без OpenGL) // Освещение: читаем данные секций (client thread, без OpenGL)
FogLightStreamer.prepareInitialLoads(); FogLightStreamer.prepareAllSections();
} }
private static void initialize() { private static void initialize() {
@@ -210,7 +210,8 @@ void main()
// Вклад ослабляется текущим transmittance // Вклад ослабляется текущим transmittance
skyScattered += lightValues.x * baseScattering * transmittance; skyScattered += lightValues.x * baseScattering * transmittance;
blockScattered += lightValues.y * baseScattering * transmittance; //Свет от блоков не должен делать значимо ярче те воксели, которые и так подсвечены солнцем. Солнечный свет очень яркий, с ним сложно сравниться
blockScattered += lightValues.y * baseScattering * transmittance*((lightValues.y-lightValues.x)*0.9+0.1);
ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance; ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance;
// Обновляем transmittance: Beer-Lambert law // Обновляем transmittance: Beer-Lambert law