Compare commits

..

10 Commits

Author SHA1 Message Date
cola98765 2834a73551 fix fog for fixed render distance 2021-12-10 20:06:22 +01:00
cola98765 5a7af89f5c fix calculation of view range. if it was k*32+x where k is int and 0<=x<16. To compare with old versions add 32 2021-12-10 20:06:14 +01:00
cola98765 eff491127b changed debug key to F8 and added chat messages when using debug keys. 2021-12-10 20:06:04 +01:00
Morippi 61502c32a6 fixewd generation not working 2021-12-10 19:18:22 +01:00
Morippi b7911ec4c8 Removed most of BlockPos and ChunkPos use 2021-12-10 19:05:27 +01:00
cola98765 1115c5f6c5 moved merge works now 19min on my PC 2021-12-10 09:28:10 +01:00
Morippi 5d001bf7ea changed some parameters 2021-12-09 20:26:19 +01:00
Morippi 4e732f7916 added a parameter 2021-12-09 20:25:55 +01:00
Morippi 30d392f861 Added field name and methods name for the merge 2021-12-09 20:25:01 +01:00
cola98765 f8910b0c3b move the merge 2021-12-09 19:59:50 +01:00
61 changed files with 2849 additions and 3943 deletions
View File
-1
View File
@@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2021-12-22T02:14:44.485Z" agent="5.0 (Windows)" etag="8Lz4CpREcKLpQpROSPVl" version="16.0.3" type="gitlab"><diagram id="xLs7mM1S-vncSruOQYJG" name="Page-1">xZVNj5swEIZ/DcetAJdkc2yTbXvZVaQcuunNtSfgrsGRcRbor6+Jx4BDom3VSr1Enmc+7HnHOBFZl+1nTY/Fo+IgozTmbUQ2UZou71P724POAZIlDuRacIcmYCd+AsIY6UlwqINAo5Q04hhCpqoKmAkY1Vo1YdhByXDXI81hBnaMyjn9KrgpHL3P4pF/AZEXfuckRk9JfTCCuqBcNRNEHiKy1koZtyrbNcheO6+Ly/t0wzscTENlfichf/r2ohqemP2Pp+3dtn3s9tkdVnml8oQNCwOlJeioTeeV0OpUceiLxRH52BQ2cHekrPc2dvSWFaaU1krsEsuCNtDePG8yqGBvD6gSjO5sCCasUDe8OMl7tJtxDInXtpiMYIGM4uTzofIojl2gPn+gVXpLq/TvtDoIKddKKn3OJYcDLBizvDZavcDEw5er73H8b9QlSSjv4n+rS2bqzlSFin/oP2lrMUnrWrBQyFB1aIV5Rk+/3vf8XYbWpp2EbTpvVLaV56nhspaZt8e8s+UT3VmBzx6Ti3nYftRJM3j7kzRU52Deuo7z+U4GmF2Zn2caJDXiNTzutaHiDlslbCM3r89Q15dwbWLW9FW6LLS4+MxXF4WcDrNC5zs2tH3t2llzfFxd+PgPRR5+AQ==</diagram></mxfile>
@@ -61,7 +61,6 @@ public class ClientApi
private boolean firstTimeSetupComplete = false;
private boolean configOverrideReminderPrinted = false;
public boolean rendererDisabledBecauseOfExceptions = false;
private ClientApi()
@@ -113,17 +112,9 @@ public class ClientApi
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
if (!rendererDisabledBecauseOfExceptions) {
try {
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
} catch (RuntimeException e) {
rendererDisabledBecauseOfExceptions = true;
try {
//ClientApi.renderer.ma ();
} catch (RuntimeException welpLookLikeWeWillLeakResource) {}
throw e;
}
}
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
}
@@ -182,8 +173,6 @@ public class ClientApi
// Lod maintenance //
//=================//
// FIXME: I need a onLastFrameCleanup() callback in Render Thread... Which calls renderer.cleanup()
/** This event is called once during the first frame Minecraft renders in the world. */
public void firstFrameSetup()
{
@@ -21,7 +21,7 @@ package com.seibel.lod.core.api;
import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.LodGenWorker;
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
@@ -81,8 +81,7 @@ public class EventApi
if (lodDim == null)
return;
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ClientApi.renderer, ApiShared.lodBuilder);
}
@@ -106,8 +105,6 @@ public class EventApi
public void worldLoadEvent(IWorldWrapper world)
{
DataPointUtil.WORLD_HEIGHT = world.getHeight();
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
@@ -124,8 +121,6 @@ public class EventApi
{
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
// ClientApi.renderer.markForCleanup();
// ClientApi.renderer.destroyBuffers();
new Thread(() -> checkIfDisconnectedFromServer()).start();
}
@@ -152,7 +147,7 @@ public class EventApi
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
LodWorldGenerator.INSTANCE.restartExecutorService();
LodGenWorker.restartExecutorService();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
ApiShared.lodWorld.deselectWorld();
@@ -161,11 +156,9 @@ public class EventApi
// prevent issues related to the buffer builder
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
recalculateWidths = true;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
// make sure the nulled objects are freed.
// (this prevents an out of memory error when
@@ -20,7 +20,6 @@
package com.seibel.lod.core.builders.bufferBuilding;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -29,11 +28,14 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import com.seibel.lod.core.enums.config.LodTemplate;
import net.minecraft.util.math.BlockPos;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ClientApi;
@@ -57,6 +59,9 @@ import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -65,40 +70,26 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
* rendered by the LodRenderer.
*
* @author James Seibel
* @version 12-9-2021
* @version 12-8-2021
*/
public class LodBufferBuilderFactory
{
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
public void end(String source) {
timer = System.nanoTime() - timer;
if (timer> 16000000) { //16 ms
ClientApi.LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
}
}
}
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
/** The thread used to generate new LODs off the main thread. */
public static final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilderFactory.class.getSimpleName() + " - main"));
/** The threads used to generate buffers. */
public static final ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), new ThreadFactoryBuilder().setNameFormat("Buffer-Builder-%d").build());
public static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
/**
* When uploading to a buffer that is too small,
* recreate it this many times bigger than the upload payload
*/
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3;
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.5;
/**
* When buffers are first created they are allocated to this size (in Bytes).
@@ -155,6 +146,8 @@ public class LodBufferBuilderFactory
private volatile VertexOptimizer[][] vertexOptimizerCache;
private volatile PosToRenderContainer[][] setsToRender;
private volatile int centerRegionX = 0;
private volatile int centerRegionZ = 0;
/**
* This is the ChunkPosWrapper the player was at the last time the buffers were built.
@@ -162,8 +155,8 @@ public class LodBufferBuilderFactory
*/
private volatile int drawableCenterChunkPosX = 0;
private volatile int drawableCenterChunkPosZ = 0;
private volatile int buildableCenterBlockPosX = 0;
private volatile int buildableCenterBlockPosZ = 0;
private volatile int buildableCenterChunkPosX = 0;
private volatile int buildableCenterChunkPosZ = 0;
@@ -221,8 +214,8 @@ public class LodBufferBuilderFactory
// round the player's block position down to the nearest chunk BlockPos
int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.CHUNK_DETAIL_LEVEL);
int playerChunkZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.CHUNK_DETAIL_LEVEL);
//int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.REGION_DETAIL_LEVEL);
//int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.REGION_DETAIL_LEVEL);
int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.REGION_DETAIL_LEVEL);
int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.REGION_DETAIL_LEVEL);
//long startTime = System.currentTimeMillis();
@@ -244,10 +237,8 @@ public class LodBufferBuilderFactory
vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()];
// this will be the center of the VBOs once they have been built
//buildableCenterChunkPosX = playerChunkX;
//buildableCenterChunkPosZ = playerChunkZ;
buildableCenterBlockPosX = playerX;
buildableCenterBlockPosZ = playerZ;
buildableCenterChunkPosX = playerChunkX;
buildableCenterChunkPosZ = playerChunkZ;
//================================//
@@ -327,8 +318,6 @@ public class LodBufferBuilderFactory
// keep a local version, so we don't have to worry about indexOutOfBounds Exceptions
// if it changes in the LodRenderer while we are working here
// FIXME: THIS IS NOT HOW IT WORKS! We also can't just loop and copy it. Think of an
// idea to fix this!
boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks;
short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1);
@@ -344,7 +333,6 @@ public class LodBufferBuilderFactory
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ;
// FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide!
//We don't want to render this fake block if
//The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
//
@@ -433,7 +421,7 @@ public class LodBufferBuilderFactory
break;
//We send the call to create the vertices
CubicLodTemplate.addLodToBuffer(currentBuffers[bufferIndex], playerX, playerZ, data, adjData,
LodTemplate.CUBIC.template.addLodToBuffer(currentBuffers[bufferIndex], playerX, playerY, playerZ, data, adjData,
detailLevel, posX, posZ, vertexOptimizer, renderer.previousDebugMode, adjShadeDisabled);
}
@@ -623,27 +611,27 @@ public class LodBufferBuilderFactory
// create the initial mapped buffers (system memory)
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (glProxy.bufferStorageSupported)
{
// create the buffer storage (GPU memory)
buildableStorageBufferIds[x][z][i] = GL44.glGenBuffers();
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT);
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0);
buildableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); // the 0 flag means to create the storage in the GPUs memory
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
drawableStorageBufferIds[x][z][i] = GL44.glGenBuffers();
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT);
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0);
drawableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
}
@@ -663,73 +651,106 @@ public class LodBufferBuilderFactory
}
/**
* Sets the buffers and Vbos to null, forcing them to be recreated <br>
* and destroys any bound OpenGL objects. <br>
* <br>
* and destroys any bound OpenGL objects. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void destroyBuffers() {
int[][][] toBeDeletedBuildableStorageBufferIds;
int[][][] toBeDeletedDrawableStorageBufferIds;
LodVertexBuffer[][][] toBeDeletedBuildableVbos;
LodVertexBuffer[][][] toBeDeletedDrawableVbos;
bufferLock.lock();
try {
toBeDeletedBuildableStorageBufferIds = buildableStorageBufferIds;
toBeDeletedDrawableStorageBufferIds = drawableStorageBufferIds;
toBeDeletedBuildableVbos = buildableVbos;
toBeDeletedDrawableVbos = drawableVbos;
public void destroyBuffers()
{
try
{
bufferLock.lock();
// destroy the buffer storages if they aren't already
if (buildableStorageBufferIds != null)
{
for (int x = 0; x < buildableStorageBufferIds.length; x++)
{
for (int z = 0; z < buildableStorageBufferIds.length; z++)
{
for (int i = 0; i < buildableStorageBufferIds[x][z].length; i++)
{
int buildableId = buildableStorageBufferIds[x][z][i];
int drawableId = drawableStorageBufferIds[x][z][i];
// make sure the buffers are deleted in a openGL context
GLProxy.getInstance().recordOpenGlCall(() ->
{
GL15.glDeleteBuffers(buildableId);
GL15.glDeleteBuffers(drawableId);
});
}
}
}
}
buildableStorageBufferIds = null;
drawableStorageBufferIds = null;
// destroy the VBOs if they aren't already
if (buildableVbos != null)
{
for (int i = 0; i < buildableVbos.length; i++)
{
for (int j = 0; j < buildableVbos.length; j++)
{
for (int k = 0; k < buildableVbos[i][j].length; k++)
{
int buildableId;
int drawableId;
// variables passed into a lambda expression
// need to be effectively final, so we have
// to use an else statement here
if (buildableVbos[i][j][k] != null)
buildableId = buildableVbos[i][j][k].id;
else
buildableId = 0;
if (drawableVbos[i][j][k] != null)
drawableId = drawableVbos[i][j][k].id;
else
drawableId = 0;
GLProxy.getInstance().recordOpenGlCall(() ->
{
if (buildableId != 0)
GL15.glDeleteBuffers(buildableId);
if (drawableId != 0)
GL15.glDeleteBuffers(drawableId);
});
}
}
}
}
buildableVbos = null;
drawableVbos = null;
// these don't contain any OpenGL objects, so
// they don't require any special clean-up
buildableBuffers = null;
} finally {
}
catch (Exception e)
{
ClientApi.LOGGER.info("destroyBuffers ran into trouble: " + e.getMessage(), e);
}
finally
{
// this shouldn't normally happen, but just in case it sill prevent deadlock
bufferLock.unlock();
}
// make sure the buffers are deleted in a openGL context
GLProxy.getInstance().recordOpenGlCall(() -> {
// destroy the buffer storages if they aren't already
if (toBeDeletedBuildableStorageBufferIds != null) {
for (int x = 0; x < toBeDeletedBuildableStorageBufferIds.length; x++) {
for (int z = 0; z < toBeDeletedBuildableStorageBufferIds.length; z++) {
for (int i = 0; i < toBeDeletedBuildableStorageBufferIds[x][z].length; i++) {
int buildableId = toBeDeletedBuildableStorageBufferIds[x][z][i];
int drawableId = toBeDeletedDrawableStorageBufferIds[x][z][i];
GL32.glDeleteBuffers(buildableId);
GL32.glDeleteBuffers(drawableId);
}
}
}
}
// destroy the VBOs if they aren't already
if (toBeDeletedBuildableVbos != null) {
for (int i = 0; i < toBeDeletedBuildableVbos.length; i++) {
for (int j = 0; j < toBeDeletedBuildableVbos.length; j++) {
for (int k = 0; k < toBeDeletedBuildableVbos[i][j].length; k++) {
if (toBeDeletedBuildableVbos[i][j][k] != null) {
int buildableId = toBeDeletedBuildableVbos[i][j][k].id;
GL32.glDeleteBuffers(buildableId);
}
if (toBeDeletedDrawableVbos[i][j][k] != null) {
int drawableId = toBeDeletedDrawableVbos[i][j][k].id;
GL32.glDeleteBuffers(drawableId);
}
}
}
}
}
});
}
/** Calls begin on each of the buildable BufferBuilders. */
private void startBuffers(boolean fullRegen, LodDimension lodDim)
{
@@ -741,12 +762,12 @@ public class LodBufferBuilderFactory
{
for (int i = 0; i < buildableBuffers[x][z].length; i++)
{
// FIXME: for some reason BufferBuilder.vertexCounts
// for some reason BufferBuilder.vertexCounts
// isn't reset unless this is called, which can cause
// a false indexOutOfBoundsException
buildableBuffers[x][z][i].discard();
buildableBuffers[x][z][i].begin(GL32.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
}
}
}
@@ -768,6 +789,8 @@ public class LodBufferBuilderFactory
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
{
GLProxy glProxy = GLProxy.getInstance();
long fence = 0;
try
{
// make sure we are uploading to the builder context,
@@ -775,12 +798,25 @@ public class LodBufferBuilderFactory
glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
// determine the upload method
GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod();
GpuUploadMethod uploadMethod = CONFIG.client().advanced().buffers().getGpuUploadMethod();
if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to SUB_DATA
CONFIG.client().advanced().buffers().setGpuUploadMethod(GpuUploadMethod.SUB_DATA);
uploadMethod = GpuUploadMethod.SUB_DATA;
}
// determine the upload timeout
int MBPerMS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
long BPerNS = MBPerMS; // MB -> B = 1/1,000,000. MS -> NS = 1,000,000. So, MBPerMS = BPerNS.
long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS.
int uploadTimeoutInMS = CONFIG.client().advanced().buffers().getGpuUploadTimeoutInMilliseconds();
// James has no idea if this does anything helpful,
// but in theory it should prevent OpenGL from drawing and
// writing to a buffer at the same time.
GL45.glMemoryBarrier(GL45.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
fence = GL45.glFenceSync(GL45.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// actually upload the buffers
for (int x = 0; x < buildableVbos.length; x++)
@@ -791,57 +827,46 @@ public class LodBufferBuilderFactory
{
for (int i = 0; i < buildableBuffers[x][z].length; i++)
{
ByteBuffer uploadBuffer = null;
//FIXME: The sonme Buffers aren't closed/end() and causing errors!
try {
LagSpikeCatcher b = new LagSpikeCatcher();
uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
b.end("getCleanedByteBuffer");
} catch (IndexOutOfBoundsException e) {
// NOTE: Temp try/catch for above FIXME.
// e.printStackTrace();
}
if (uploadBuffer == null) continue;
if (uploadBuffer.capacity() == 0) continue;
LagSpikeCatcher vboU = new LagSpikeCatcher();
vboUpload(x,z,i, uploadBuffer, uploadMethod);
vboU.end("vboUpload");
LagSpikeCatcher setR = new LagSpikeCatcher();
ByteBuffer uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
vboUpload(x,z,i, uploadBuffer, true, uploadMethod);
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
setR.end("setRegenRegionBufferByArrayIndex");
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += uploadBuffer.capacity()*BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000/60, TimeUnit.MILLISECONDS)) {
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
Thread.sleep(remainingNS/1000000, (int) (remainingNS%1000000));
remainingNS = 0;
}
if (uploadTimeoutInMS != 0)
Thread.sleep(uploadTimeoutInMS);
GL15.glFinish();
}
}
}
}
// make sure all of the uploads finish before continuing
GL45.glClientWaitSync(fence, GL45.GL_SYNC_FLUSH_COMMANDS_BIT, 5L * 1000000000); // wait up to 5 seconds
}
catch (Exception e)
{
// this doesn't appear to be necessary anymore, but just in case.
ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
e.printStackTrace();
} finally {
// newSingleThreadExecutor doesn't mean that all jobs will be on a single, same
// thread. It just means that it can at most use one thread. If there are no
// jobs for a certain amount of time, or something happened when a job is
// executing, it could decide to delete the thread, and create a new one for the
// next job. So we will need to release the gl context.
LagSpikeCatcher end = new LagSpikeCatcher();
}
finally
{
GL15.glFinish();
if (fence != 0)
GL45.glDeleteSync(fence);
// close the context so it can be re-used later.
// I'm guessing we can't just leave it because the executor service
// does something that invalidates the OpenGL context.
glProxy.setGlContext(GLProxyContext.NONE);
end.end("GLSwitchContext");
}
}
/** Uploads the uploadBuffer so the GPU can use it. */
private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod)
private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer,
boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
{
// get the vbos, buffers, ids, etc.
int storageBufferId = 0;
@@ -850,101 +875,115 @@ public class LodBufferBuilderFactory
LodVertexBuffer vbo = buildableVbos[xIndex][zIndex][iIndex];
// this shouldn't happen, but just to be safe
if (vbo.id != -1 && GLProxy.getInstance().getGlContext() == GLProxyContext.LOD_BUILDER)
{
// this is how many points will be rendered
vbo.vertexCount = (uploadBuffer.capacity() / LodUtil.LOD_VERTEX_FORMAT.getByteSize());
// If size is zero, just ignore it.
if (uploadBuffer.capacity()==0) return;
LagSpikeCatcher bindBuff = new LagSpikeCatcher();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
bindBuff.end("glBindBuffer vbo.id");
vbo.vertexCount = (uploadBuffer.capacity() / ((Float.BYTES * 3) + (Byte.BYTES * 4) + Byte.BYTES + Byte.BYTES)); // TODO make this change with the LodTemplate
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
try
{
// if possible use the faster buffer storage route
if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0)
{
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId);
LagSpikeCatcher getParm = new LagSpikeCatcher();
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
getParm.end("glGetBufferParameteri BuffStorage");
if (size < uploadBuffer.capacity())
// get a pointer to the buffer in system memory
ByteBuffer vboBuffer = GL30.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
if (vboBuffer == null)
{
int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher();
GL32.glDeleteBuffers(storageBufferId);
buildableStorageBufferIds[xIndex][zIndex][iIndex] = GL32.glGenBuffers();
buffResizeRegen.end("glDeleteBuffers BuffStorage resize");
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId);
storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex];
LagSpikeCatcher buffResize = new LagSpikeCatcher();
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT);
buffResize.end("glBufferStorage BuffStorage resize");
int previousCapacity = uploadBuffer.capacity();
// only expand the buffers if the uploadBuffer actually
// has something in it and expansion is allowed
if (previousCapacity != 0 && allowBufferExpansion)
{
// the buffer(s) aren't big enough, expand them.
// This does cause lag/stuttering, so it should be avoided!
// expand the buffer in system memory
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
// un-bind the system memory buffer
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// expand the buffer storage
GL15.glDeleteBuffers(storageBufferId);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, storageBufferId);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// recursively try to upload into the newly created buffer storage
// but don't recurse again if that fails
// (we don't want an infinitely expanding buffer!)
vboUpload(xIndex,zIndex,iIndex, uploadBuffer, false, uploadMethod);
}
}
else
{
// upload the buffer into system memory...
vboBuffer.put(uploadBuffer);
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
// ...then upload into GPU memory
// (uploading into GPU memory directly can only be done
// through the glCopyBufferSubData/glCopyNamed... methods)
GL45.glCopyNamedBufferSubData(vbo.id, storageBufferId, 0, 0, uploadBuffer.capacity());
}
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
buffSubData.end("glBufferSubData BuffStorage");
}
else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
{
// TODO: Check this half reasonable comment!
// no stuttering but high GPU usage
// stores everything in system memory instead of GPU memory
// making rendering much slower.
// Unless the user is running integrated graphics,
// in that case this will actually work better than SUB_DATA.
LagSpikeCatcher getParm = new LagSpikeCatcher();
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
getParm.end("glGetBufferParameteri BuffMapping");
if (size < uploadBuffer.capacity())
{
int newSize = (int) (uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
LagSpikeCatcher buffResize = new LagSpikeCatcher();
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
buffResize.end("glBufferData BuffMapping resize");
}
ByteBuffer vboBuffer;
// map buffer range is better since it can be explicitly unsynchronized
LagSpikeCatcher buffMap = new LagSpikeCatcher();
vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(),
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
buffMap.end("glMapBufferRange BuffMapping");
LagSpikeCatcher buffWrite = new LagSpikeCatcher();
vboBuffer.put(uploadBuffer);
buffWrite.end("WriteData BuffMapping");
// map buffer range is better since it can be explicitly unsynchronized
if (GLProxy.getInstance().mapBufferRangeSupported)
vboBuffer = GL30.glMapBufferRange(GL30.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT);
else
vboBuffer = GL15.glMapBuffer(GL30.GL_ARRAY_BUFFER, uploadBuffer.capacity());
if (vboBuffer == null)
{
GL15.glBufferData(GL45.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
}
else
{
vboBuffer.put(uploadBuffer);
}
}
else if (uploadMethod == GpuUploadMethod.DATA)
{
// TODO: Check this nonsense comment!
// hybrid bufferData //
// high stutter, low GPU usage
// But simplest/most compatible
LagSpikeCatcher buffData = new LagSpikeCatcher();
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW);
buffData.end("glBufferData Data");
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.capacity(), GL15.GL_STATIC_DRAW);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15.GL_STATIC_DRAW);
}
else
{
// TODO: Check this nonsense comment!
// hybrid subData/bufferData //
// less stutter, low GPU usage
LagSpikeCatcher getParm = new LagSpikeCatcher();
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
getParm.end("glGetBufferParameteri SubData");
if (size < uploadBuffer.capacity())
long size = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (size < uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER)
{
int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
LagSpikeCatcher buffResize = new LagSpikeCatcher();
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
buffResize.end("glBufferData SubData resize");
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
}
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
buffSubData.end("glBufferSubData SubData");
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
}
}
catch (Exception e)
@@ -954,14 +993,10 @@ public class LodBufferBuilderFactory
}
finally
{
LagSpikeCatcher buffUnmap = new LagSpikeCatcher();
if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
buffUnmap.end("glUnmapBuffer");
LagSpikeCatcher buffUnbind = new LagSpikeCatcher();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
buffUnbind.end("glBindBuffer 0");
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}//if vbo exists and in correct GL context
@@ -972,7 +1007,6 @@ public class LodBufferBuilderFactory
{
// don't wait for the lock to open,
// since this is called on the main render thread
// TODO: Use atomic swap instead of locks!
if (bufferLock.tryLock())
{
try
@@ -985,8 +1019,8 @@ public class LodBufferBuilderFactory
drawableStorageBufferIds = buildableStorageBufferIds;
buildableStorageBufferIds = tmpStorage;
drawableCenterChunkPosX = buildableCenterBlockPosX;
drawableCenterChunkPosZ = buildableCenterBlockPosZ;
drawableCenterChunkPosX = buildableCenterChunkPosX;
drawableCenterChunkPosZ = buildableCenterChunkPosZ;
// the vbos have been swapped
switchVbos = false;
@@ -1010,15 +1044,15 @@ public class LodBufferBuilderFactory
{
public final LodVertexBuffer[][][] vbos;
public final int[][][] storageBufferIds;
public int drawableCenterBlockPosX;
public int drawableCenterBlockPosZ;
public int drawableCenterChunkPosX;
public int drawableCenterChunkPosZ;
public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterBlockPosX, int newDrawableCenterBlockPosZ)
public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterChunkPosX, int newDrawableCenterChunkPosZ)
{
vbos = newVbos;
storageBufferIds = newStorageBufferIds;
drawableCenterBlockPosX = newDrawableCenterBlockPosX;
drawableCenterBlockPosZ = newDrawableCenterBlockPosZ;
drawableCenterChunkPosX = newDrawableCenterChunkPosX;
drawableCenterChunkPosZ = newDrawableCenterChunkPosZ;
}
}
@@ -0,0 +1,55 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* This is the abstract class used to create different
* BufferBuilders.
* @author James Seibel
* @version 12-8-2021
*/
public abstract class AbstractLodTemplate
{
/** Uploads the given LOD to the buffer. */
public abstract void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled);
/** add the given position and color to the buffer */
protected void addPosAndColor(LodBufferBuilder buffer,
float x, float y, float z,
int color, byte skyLightValue, byte blockLightValue)
{
// TODO re-add transparency by replacing the color 255 with "ColorUtil.getAlpha(color)"
buffer.position(x, y, z)
.color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255)
.minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue)
.endVertex();
}
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.bufferBuilding;
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
@@ -25,25 +25,24 @@ import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import static com.seibel.lod.core.builders.lodBuilding.LodBuilder.MIN_WORLD_HEIGHT;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 12-8-2021
*/
public class CubicLodTemplate
public class CubicLodTemplate extends AbstractLodTemplate
{
//TODO make it a config
static int cullingRange = 128;
public static void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerZ, long data, Map<LodDirection, long[]> adjData,
public CubicLodTemplate()
{
}
@Override
public void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
{
if (vertexOptimizer == null)
@@ -66,6 +65,7 @@ public class CubicLodTemplate
blockWidth,
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
playerX,
playerY,
playerZ,
adjData,
color,
@@ -76,24 +76,10 @@ public class CubicLodTemplate
addBoundingBoxToBuffer(buffer, vertexOptimizer);
}
/** add the given position and color to the buffer */
public static void addPosAndColor(LodBufferBuilder buffer,
float x, float y, float z,
int color, byte skyLightValue, byte blockLightValue)
{
// TODO transparency re-add by replacing the color 255 with "ColorUtil.getAlpha(color)"
buffer.position(x, y, z)
.color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255)
.minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue)
.endVertex();
}
private static void generateBoundingBox(VertexOptimizer vertexOptimizer,
private void generateBoundingBox(VertexOptimizer vertexOptimizer,
int height, int depth, int width,
double xOffset, double yOffset, double zOffset,
int playerX, int playerZ,
int playerX, int playerY, int playerZ,
Map<LodDirection, long[]> adjData,
int color, byte skyLight, byte blockLight,
boolean[] adjShadeDisabled)
@@ -120,24 +106,13 @@ public class CubicLodTemplate
vertexOptimizer.setAdjData(adjData);
}
private static void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
private void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
{
int color;
byte skyLight;
byte blockLight;
for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS)
{
//if(vertexOptimizer.isCulled(lodDirection))
// continue;
// culling
if (lodDirection == LodDirection.NORTH && vertexOptimizer.getZ(lodDirection, 0) < -cullingRange
|| lodDirection == LodDirection.EAST && vertexOptimizer.getX(lodDirection, 0) > cullingRange
|| lodDirection == LodDirection.SOUTH && vertexOptimizer.getZ(lodDirection, 0) > cullingRange
|| lodDirection == LodDirection.WEST && vertexOptimizer.getX(lodDirection, 0) < -cullingRange)
continue;
int verticalFaceIndex = 0;
while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex))
{
@@ -148,7 +123,7 @@ public class CubicLodTemplate
color = vertexOptimizer.getColor(lodDirection);
addPosAndColor(buffer,
vertexOptimizer.getX(lodDirection, vertexIndex),
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + MIN_WORLD_HEIGHT,
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + DataPointUtil.VERTICAL_OFFSET,
vertexOptimizer.getZ(lodDirection, vertexIndex),
color, skyLight, blockLight );
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* TODO DynamicLodTemplate
* Chunks smoothly transition between
* each other, unless a neighboring chunk
* is at a significantly different height.
* @author James Seibel
* @version 06-16-2021
*/
public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* TODO #21 TriangularLodTemplate
* Builds each LOD chunk as a singular rectangular prism.
* @author James Seibel
* @version 06-16-2021
*/
public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -35,9 +35,12 @@ import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -51,18 +54,18 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* @author Cola
* @author Leonardo Amato
* @author James Seibel
* @version 12-11-2021
* @version 10-22-2021
*/
@SuppressWarnings("GrazieInspection")
public class LodBuilder
@SuppressWarnings("GrazieInspection") public class LodBuilder
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
/** This cannot be final! Different world have different height, and in menu, this causes Null Exceptions*/
//public static final short MIN_WORLD_HEIGHT = MC.getWrappedClientWorld().getMinHeight();
public static short MIN_WORLD_HEIGHT = 0; // Currently modified in EventApi.onWorldLoaded(...)
/** If no blocks are found in the area in determineBottomPointForArea return this */
public static final short DEFAULT_DEPTH = 0;
/** If no blocks are found in the area in determineHeightPointForArea return this */
public static final short DEFAULT_HEIGHT = 0;
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
@@ -80,7 +83,6 @@ public class LodBuilder
//public static final boolean useExperimentalLighting = true;
private static int timesToEdgeDetect = 1;
@@ -108,8 +110,8 @@ public class LodBuilder
Thread thread = new Thread(() ->
{
//noinspection GrazieInspection
//try
//{
try
{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
@@ -132,14 +134,14 @@ public class LodBuilder
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
//}
//catch (IllegalArgumentException | NullPointerException e)
//{
// e.printStackTrace();
// // if the world changes while LODs are being generated
// // they will throw errors as they try to access things that no longer
// // exist.
//}
}
catch (IllegalArgumentException | NullPointerException e)
{
e.printStackTrace();
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
});
lodGenThreadPool.execute(thread);
}
@@ -196,16 +198,9 @@ public class LodBuilder
long[] data;
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
}
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
lodDim.mergeMultiData(detailLevel, posX, posZ, false, dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1);
}
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getChunkPosX(), chunk.getChunkPosZ());
//executeTime = System.currentTimeMillis() - executeTime;
@@ -246,38 +241,40 @@ public class LodBuilder
zAbs = chunk.getMinZ() + zRel;
//Calculate the height of the lod
yAbs = chunk.getMaxY(xRel,zRel) - MIN_WORLD_HEIGHT;
yAbs = chunk.getMaxY(xRel, zRel); //DataPointUtil.WORLD_HEIGHT - DataPointUtil.VERTICAL_OFFSET + 1;
int count = 0;
boolean topBlock = true;
if (yAbs <= 0);
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
while (yAbs > 0)
{
height = determineHeightPointFrom(chunk, config, xAbs, yAbs, zAbs);
// If the lod is at the default height, it must be void data
if (height == 0)
if (height == DEFAULT_HEIGHT)
{
if (topBlock)
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
break;
}
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs, count < timesToEdgeDetect && !hasCeiling);
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs);
if (hasCeiling && topBlock)
{
yAbs = depth;
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, true, hasSkyLight, true);
light = getLightValue(chunk, xAbs,yAbs,zAbs, true, hasSkyLight, true);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
else
{
light = getLightValue(chunk, xAbs, yAbs + MIN_WORLD_HEIGHT, zAbs, hasCeiling, hasSkyLight, topBlock);
light = getLightValue(chunk, xAbs, yAbs, zAbs, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
isDefault = ((light >> 8)) == 1;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height - DataPointUtil.VERTICAL_OFFSET, depth - DataPointUtil.VERTICAL_OFFSET, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
@@ -290,40 +287,17 @@ public class LodBuilder
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
short depth = 0;
short depth = DEFAULT_DEPTH;
int colorOfBlock = 0;
if (strictEdge)
for (int y = yAbs; y >= 0; y--)
{
colorOfBlock = chunk.getBlockColorWrapper(xAbs, yAbs, zAbs).getColor();
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(xAbs, yAbs + 1, zAbs);
if (block != null && ((this.config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|| (this.config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
{
int aboveColorInt = chunk.getBlockColorWrapper(xAbs, yAbs + 1, zAbs).getColor();
if (aboveColorInt != 0)
colorOfBlock = aboveColorInt;
}
}
for (int y = yAbs - 1; y >= 0; y--)
{
if (!isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
depth = (short) (y + 1);
break;
}
if (strictEdge)
{
if (colorOfBlock != chunk.getBlockColorWrapper(xAbs, y, zAbs).getColor())
{
depth = (short) (y + 1);
break;
}
}
}
return depth;
}
@@ -331,8 +305,8 @@ public class LodBuilder
/** Find the highest valid point from the Top */
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
//TODO find a way to skip bottom of the world
short height = 0;
short height = DEFAULT_HEIGHT;
if (config.useHeightmap)
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
else
@@ -376,10 +350,10 @@ public class LodBuilder
// snow, flowers, etc. Get the above block so we can still get the color
// of the snow, flower, etc. that may be above this block
int aboveColorInt = 0;
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y + 1, z);
if (block != null && ((config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|| (config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
aboveColorInt = getColorForBlock(chunk, x, y + 1, z);
if (config.client().worldGenerator().getBlocksToAvoid().nonFull || config.client().worldGenerator().getBlocksToAvoid().noCollision)
{
aboveColorInt = getColorForBlock(chunk, x, y, z);
}
//if (colorInt == 0 && yAbs > 0)
// if this block is invisible, check the block below it
@@ -491,19 +465,17 @@ public class LodBuilder
int colorOfBlock;
int colorInt;
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
if (blockShapeWrapper == null || blockShapeWrapper.isToAvoid())
return 0;
IBlockColorWrapper blockColorWrapper;
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
if (chunk.isWaterLogged(x, y, z))
blockColorWrapper = BLOCK_COLOR.getWaterColor();
else
blockColorWrapper = chunk.getBlockColorWrapper(x, y, z);
if (blockShapeWrapper.isToAvoid())
return 0;
colorOfBlock = blockColorWrapper.getColor();
@@ -539,7 +511,6 @@ public class LodBuilder
boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision;
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y, z);
if (block == null) return false;
return !block.isToAvoid()
&& !(nonFullAvoidance && block.isNonFull())
&& !(noCollisionAvoidance && block.hasNoCollision());
@@ -0,0 +1,212 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.worldGeneration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 11-20-2021
*/
public class LodGenWorker
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public static ExecutorService genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
private final LodChunkGenThread thread;
public LodGenWorker(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper serverWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newGenerationMode,
newLodBuilder,
newLodDimension, serverWorld);
}
public void queueWork()
{
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL)
{
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
// because MC's internal server (as of 1.16.5) only
// responds with a single thread. And we don't
// want to cause more lag than necessary or queue up
// requests that may end up being unneeded.
thread.run();
}
else
{
// Every other method can
// be done asynchronously
genThreads.execute(thread);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private static class LodChunkGenThread implements Runnable
{
private final AbstractWorldGeneratorWrapper worldGenWrapper;
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
private final AbstractChunkPosWrapper pos;
public LodChunkGenThread(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper worldWrapper)
{
worldGenWrapper = FACTORY.createWorldGenerator(newLodBuilder, newLodDimension, worldWrapper);
pos = newPos;
generationMode = newGenerationMode;
lodDim = newLodDimension;
}
@Override
public void run()
{
//try
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
worldGenWrapper.generateBiomesOnly(pos, generationMode);
break;
case SURFACE:
// faster
worldGenWrapper.generateSurface(pos);
break;
case FEATURES:
// fast
worldGenWrapper.generateFeatures(pos);
break;
case FULL:
// very slow
worldGenWrapper.generateFull(pos);
break;
}
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
// if (dataExistence)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}
//catch (Exception e)
//{
// ClientApi.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage());
// e.printStackTrace();
//}
//finally
//{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
//}
}// run
}
/**
* Stops the current genThreads if they are running
* and then recreates the Executor service. <br><br>
* <p>
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public static void restartExecutorService()
{
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
@@ -25,47 +25,50 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* A singleton that handles all long distance LOD world generation.
* @author Leonardo Amato
* @author James Seibel
* @version 12-11-2021
* @version 9-25-2021
*/
public class LodWorldGenerator
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/** This holds the thread used to create LOD generation requests off the main thread. */
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
private ExecutorService genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* How many chunks to generate outside the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy, and we can generate LODs as quickly as
* possible.
*/
public int maxChunkGenRequests;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
@@ -79,64 +82,32 @@ public class LodWorldGenerator
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
public AbstractExperimentalWorldGeneratorWrapper experimentalWorldGenerator;
private LodWorldGenerator() {}
private LodWorldGenerator()
{
}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
* renderer needed so the LodNodeGenWorkers can flag that the
* @param renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
{
IWorldWrapper world = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// TODO: Rename the config option
if (CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration()) {
if (experimentalWorldGenerator == null) {
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
if (experimentalWorldGenerator == null) CONFIG.client().worldGenerator().setAllowUnstableFeatureGeneration(false);
}
} else {
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
}
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.queueGenerationRequests(lodDim, lodBuilder);
return;
}
// TODO: This currently doesn't use the DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
// This is fine currently since DistanceGenerationMode doesn't care about the detail level for now.
// However, If that was to be changed, This will need to be fixed.
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
if (mode != DistanceGenerationMode.NONE
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& MC.hasSinglePlayerServer())
{
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
/**
* How many chunks to generate outside the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy, and we can generate LODs as quickly as
* possible.
*/
int genRequestPerThread = VERSION_CONSTANTS.getWorldGenerationCountPerThread();
int maxChunkGenRequests;
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * genRequestPerThread;
else maxChunkGenRequests = genRequestPerThread;
Runnable generatorFunc = (() ->
// just in case the config changed
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8;
Thread generatorThread = new Thread(() ->
{
try
{
@@ -156,6 +127,7 @@ public class LodWorldGenerator
playerPosX,
playerPosZ);
byte detailLevel;
int posX;
int posZ;
@@ -168,7 +140,7 @@ public class LodWorldGenerator
// an easy way to do so.
// add the near positions
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
@@ -187,12 +159,13 @@ public class LodWorldGenerator
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
genWorker.queueWork();
}
// add the far positions
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
@@ -212,12 +185,13 @@ public class LodWorldGenerator
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
genWorker.queueWork();
}
}
}
catch (RuntimeException e)
catch (Exception e)
{
// this shouldn't ever happen, but just in case
e.printStackTrace();
@@ -228,146 +202,8 @@ public class LodWorldGenerator
}
});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
{
generatorFunc.run();
}
else
{
mainGenThread.execute(generatorFunc);
}
mainGenThread.execute(generatorThread);
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
} // queueGenerationRequests
private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper serverWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
Runnable method = (() -> {generateChunk(newPos, newGenerationMode,
newLodBuilder, newLodDimension, serverWorld);});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode))
{
// --Note: This is now using version constants--
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
// because MC's internal server (as of 1.16.5) only
// responds with a single thread. And we don't
// want to cause more lag than necessary or queue up
// requests that may end up being unneeded.
// In 1.17+, world generation becomes completely single
// threaded. So to allow that, we check the boolean for
// whether the wrapper requires single thread
method.run();
}
else
{
// Every other method can
// be done asynchronously
genSubThreads.execute(method);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper)
{
// try
{
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim, worldWrapper);
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
worldGenWrapper.generateBiomesOnly(pos, generationMode);
break;
case SURFACE:
// faster
worldGenWrapper.generateSurface(pos);
break;
case FEATURES:
// fast
worldGenWrapper.generateFeatures(pos);
break;
case FULL:
// very slow
worldGenWrapper.generateFull(pos);
break;
}
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
// if (dataExistence)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
}// if in range
}
// catch (Exception e)
// {
// ClientApi.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into an error: " + e.getMessage());
// e.printStackTrace();
// }
// finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
/**
* Stops the current genThreads if they are running
* and then recreates the Executor service. <br><br>
* <p>
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public void restartExecutorService()
{
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
if (genSubThreads != null && !genSubThreads.isShutdown())
{
genSubThreads.shutdownNow();
}
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
@@ -1,56 +0,0 @@
package com.seibel.lod.core.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Where the annotations for the config are defined
*
* @author coolGi2007
* @version 12-28-2021
*/
public class ConfigAnnotations {
/** a textField, button, etc. that can be interacted with */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Entry
{
String name() default "";
int width() default 150;
double minValue() default Double.MIN_NORMAL;
double maxValue() default Double.MAX_VALUE;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ScreenEntry
{
String name() default "";
int width() default 100;
}
/** Used when sorting the configs in the menu */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Category
{
String value();
}
/** Makes text (looks like @Entry but dosnt save and has no button */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment
{
}
}
@@ -11,35 +11,4 @@ public class ColorFormat
public final static long RED_MASK = 0b1111_1111;
public final static long GREEN_MASK = 0b1111_1111;
public final static long BLUE_MASK = 0b1111_1111;
public static int createColorData(int alpha, int red, int green, int blue)
{
int colorData = 0;
colorData += (alpha & ALPHA_MASK) << ALPHA_SHIFT;
colorData += (red & RED_MASK) << RED_SHIFT;
colorData += (green & GREEN_MASK) << GREEN_SHIFT;
colorData += (blue & BLUE_MASK) << BLUE_SHIFT;
return colorData;
}
public static short getAlpha(long dataPoint)
{
return (short) ((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK);
}
public static short getRed(long dataPoint)
{
return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
}
public static short getGreen(long dataPoint)
{
return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
}
public static short getBlue(long dataPoint)
{
return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
}
}
@@ -1,29 +0,0 @@
package com.seibel.lod.core.dataFormat;
public class DataMergeUtil
{
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
array[start + i] = array[start + length + i];
//remove comment to not leave garbage at the end
//array[start + packetSize + i] = 0;
}
}
public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = arraySize - start - 1; i >= 0; i--)
{
array[start + length + i] = array[start + i];
array[start + i] = 0;
}
}
}
@@ -12,27 +12,27 @@ public class LightFormat
public final static byte SKY_LIGHT_MASK = 0b1111;
public static byte formatLightAsByte(byte skyLight, byte blockLight)
public byte formatLightAsByte(byte skyLight, byte blockLight)
{
return (byte) (((skyLight & SKY_LIGHT_MASK) << (BYTE_SKY_LIGHT_SHIFT + 4)) | ((blockLight & BLOCK_LIGHT_MASK) << (BYTE_BLOCK_LIGHT_SHIFT + 4)));
return (byte) (((skyLight & SKY_LIGHT_MASK) << BYTE_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << BYTE_BLOCK_LIGHT_SHIFT));
}
public static int formatLightAsInt(byte skyLight, byte blockLight)
public int formatLightAsInt(byte skyLight, byte blockLight)
{
return ((skyLight & SKY_LIGHT_MASK) << INT_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << INT_BLOCK_LIGHT_SHIFT);
}
public static int convertByteToIntFormat(byte lights)
public int convertByteToIntFormat(byte lights)
{
return formatLightAsInt((byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK), (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK));
return 0;
}
public static byte getSkyLight(byte lights)
public byte getSkyLight(byte lights)
{
return (byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getBlockLight(byte lights)
public byte getBlockLight(byte lights)
{
return (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
}
@@ -40,46 +40,39 @@ public class PositionDataFormat
return positionData;
}
public static String toString(short verticalData, short positionData)
{
return getLodCount(verticalData) + " " +
getLightFlag(verticalData) + " " +
getGenerationMode(verticalData) + " " +
isVoid(verticalData) + " " +
doesItExist(verticalData) + " " +'\n';
}
public static byte getLodCount(short dataPoint)
{
return (byte) ((dataPoint >>> LOD_COUNT_SHIFT) & LOD_COUNT_MASK);
}
public static boolean getFlag(short dataPoint)
public static boolean getLightFlag(short dataPoint)
{
return ((dataPoint >>> CORRECT_LIGHT_SHIFT) & CORRECT_LIGHT_MASK) == 1;
}
public static byte getGenerationMode(short dataPoint)
{
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
}
public static boolean isVoid(short dataPoint)
{
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
}
public static boolean doesItExist(short dataPoint)
{
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static short setLodCount(short dataPoint, short lodCount)
{
return (short) (dataPoint | ((lodCount & LOD_COUNT_MASK) << LOD_COUNT_SHIFT));
}
public static short setFlag(short dataPoint)
{
return (short) (dataPoint | ((CORRECT_LIGHT_MASK) << CORRECT_LIGHT_SHIFT));
}
public static short setGenerationMode(short dataPoint, byte generationMode)
{
return (short) (dataPoint | ((generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT));
}
public static short setVoid(short dataPoint)
{
return (short) (dataPoint | (VOID_MASK << VOID_SHIFT));
}
public static short setExistence(short dataPoint)
{
return (short) (dataPoint | (EXISTENCE_MASK << EXISTENCE_SHIFT));
}
}
@@ -4,58 +4,58 @@ public class VerticalDataFormat
{
public final static short MIN_WORLD_HEIGHT = -2048;
public final static short MAX_WORLD_HEIGHT = 2047;
public final static short WORLD_HEIGHT = MAX_WORLD_HEIGHT - MIN_WORLD_HEIGHT;
public final static byte HEIGHT_SHIFT = 20;
public final static byte DEPTH_SHIFT = 8;
public final static byte LEVEL_SHIFT = 3;
public final static byte BOTTOM_TYPE_SHIFT = 2;
public final static byte TRANSPARENCY_SHIFT = 1;
public final static byte EXISTENCE_SHIFT = 0;
public final static byte EMPTY_LOD_SHIFT = 0;
public final static int FULL_MASK = ~0;
public final static int HEIGHT_MASK = 0b1111_1111_1111;
public final static int DEPTH_MASK = 0b1111_1111_1111;
public final static int LEVEL_MASK = 0b111;
public final static int TRANSPARENCY_MASK = 0b1;
public final static int BOTTOM_TYPE_MASK = 0b1;
public final static int EXISTENCE_MASK = 0b1;
public final static int HEIGHT_RESET = ~(HEIGHT_MASK << HEIGHT_SHIFT);
public final static int DEPTH_RESET = ~(DEPTH_MASK << DEPTH_SHIFT);
public final static int LEVEL_RESET = ~(LEVEL_MASK << LEVEL_SHIFT);
public final static int TRANSPARENCY_RESET = ~(TRANSPARENCY_MASK << BOTTOM_TYPE_SHIFT);
public final static int BOTTOM_TYPE_RESET = ~(BOTTOM_TYPE_MASK << TRANSPARENCY_SHIFT);
public final static int EXISTENCE_RESET = ~(EXISTENCE_MASK << EXISTENCE_SHIFT);
public final static int EMPTY_LOD_MASK = 0b1;
public final static int EMPTY_LOD = 0;
public static int createVerticalData(int height, int depth, int level, boolean transparent, boolean bottom)
{
int verticalData = 0;
verticalData |= (height & HEIGHT_MASK) << HEIGHT_SHIFT;
verticalData |= (depth & DEPTH_MASK) << DEPTH_SHIFT;
verticalData |= ((height - MIN_WORLD_HEIGHT) & HEIGHT_MASK) << HEIGHT_SHIFT;
verticalData |= ((depth - MIN_WORLD_HEIGHT) & DEPTH_MASK) << DEPTH_SHIFT;
verticalData |= (level & LEVEL_MASK) << LEVEL_SHIFT;
if (bottom)
verticalData |= BOTTOM_TYPE_MASK << BOTTOM_TYPE_SHIFT;
if (transparent)
verticalData |= TRANSPARENCY_MASK << TRANSPARENCY_SHIFT;
verticalData |= EXISTENCE_MASK << EXISTENCE_SHIFT;
verticalData |= EMPTY_LOD_MASK << EMPTY_LOD_SHIFT;
return verticalData;
}
public static String toString(int verticalData, short positionData)
{
return getHeight(verticalData) + " " +
getDepth(verticalData) + " " +
getLevel(verticalData) + " " +
isTransparent(verticalData) + " " +
isBottom(verticalData) + " " +
doesItExist(verticalData) + " " + '\n';
}
public static short getHeight(int verticalData)
{
return (short) ((verticalData >>> HEIGHT_SHIFT) & HEIGHT_MASK);
return (short) (((verticalData >>> HEIGHT_SHIFT) & HEIGHT_MASK) + MIN_WORLD_HEIGHT);
}
public static short getDepth(int verticalData)
{
return (short) ((verticalData >>> DEPTH_SHIFT) & DEPTH_MASK);
return (short) (((verticalData >>> DEPTH_SHIFT) & DEPTH_MASK) + MIN_WORLD_HEIGHT);
}
public static byte getLevel(int verticalData)
@@ -75,37 +75,7 @@ public class VerticalDataFormat
public static boolean doesItExist(int verticalData)
{
return (((verticalData >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
return (((verticalData >>> EMPTY_LOD_SHIFT) & EMPTY_LOD_MASK) == 1);
}
public static int setHeight(int verticalData, int height)
{
return verticalData | ((height & HEIGHT_MASK) << HEIGHT_SHIFT);
}
public static int setDepth(int verticalData, int depth)
{
return verticalData | ((depth & DEPTH_MASK) << DEPTH_SHIFT);
}
public static int setLevel(int verticalData, int level)
{
return verticalData | ((level & LEVEL_MASK) << LEVEL_SHIFT);
}
public static int setTransparency(int verticalData)
{
return verticalData | ((TRANSPARENCY_MASK) << TRANSPARENCY_SHIFT);
}
public static int setBottom(int verticalData)
{
return verticalData | ((BOTTOM_TYPE_MASK) << BOTTOM_TYPE_SHIFT);
}
public static int setExistence(int verticalData)
{
return verticalData | ((EXISTENCE_MASK) << EXISTENCE_SHIFT);
}
}
@@ -19,8 +19,6 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* NONE <br>
* BIOME_ONLY <br>
@@ -94,46 +92,4 @@ public enum DistanceGenerationMode
{
this.complexity = complexity;
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode previous(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
return DistanceGenerationMode.FEATURES;
case FEATURES:
return DistanceGenerationMode.SURFACE;
case SURFACE:
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
case BIOME_ONLY_SIMULATE_HEIGHT:
return DistanceGenerationMode.BIOME_ONLY;
case BIOME_ONLY:
return DistanceGenerationMode.NONE;
case NONE:
return null;
default:
return null;
}
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode next(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
return null;
case FEATURES:
return DistanceGenerationMode.FULL;
case SURFACE:
return DistanceGenerationMode.FEATURES;
case BIOME_ONLY_SIMULATE_HEIGHT:
return DistanceGenerationMode.SURFACE;
case BIOME_ONLY:
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
case NONE:
return DistanceGenerationMode.BIOME_ONLY;
default:
return null;
}
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.TriangularLodTemplate;
/**
* Cubic, Triangular, Dynamic
*
* @author James Seibel
* @version 10-10-2021
*/
public enum LodTemplate
{
/**
* LODs are rendered as
* rectangular prisms.
*/
CUBIC(new CubicLodTemplate()),
/**
* LODs smoothly transition between
* each other.
*/
TRIANGULAR(new TriangularLodTemplate()),
/**
* LODs smoothly transition between
* each other, unless a neighboring LOD
* is at a significantly different height.
*/
DYNAMIC(new DynamicLodTemplate());
public final AbstractLodTemplate template;
LodTemplate(AbstractLodTemplate newTemplate)
{
template = newTemplate;
}
}
@@ -19,8 +19,6 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* heightmap <br>
* multi_lod <br>
@@ -79,34 +77,4 @@ public enum VerticalQuality
{
this.maxVerticalData = maxVerticalData;
}
// Note: return null if out of range
@Nullable
public static VerticalQuality previous(VerticalQuality mode) {
switch (mode) {
case HIGH:
return VerticalQuality.MEDIUM;
case MEDIUM:
return VerticalQuality.LOW;
case LOW:
return null;
default:
return null;
}
}
// Note: return null if out of range
@Nullable
public static VerticalQuality next(VerticalQuality mode) {
switch (mode) {
case HIGH:
return null;
case MEDIUM:
return VerticalQuality.HIGH;
case LOW:
return VerticalQuality.MEDIUM;
default:
return null;
}
}
}
@@ -20,6 +20,7 @@
package com.seibel.lod.core.handlers;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
/**
* A singleton used to get variables from methods
@@ -34,7 +35,7 @@ import com.seibel.lod.core.enums.rendering.FogDrawMode;
* different MC versions.
*
* @author James Seibel
* @version 12-14-2021
* @version 11-26-2021
*/
public interface IReflectionHandler
{
@@ -44,6 +45,14 @@ public interface IReflectionHandler
/** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
boolean vivecraftPresent();
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
boolean sodiumPresent();
/**
* Modifies the projection matrix's clip planes.
* The projection matrix must be in column-major format.
*
* @param projectionMatrix The projection matrix to be modified.
* @param newNearClipPlane the new near clip plane value.
* @param newFarClipPlane the new far clip plane value.
* @return The modified matrix.
*/
Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
}
@@ -29,7 +29,6 @@ import java.util.concurrent.Executors;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.jetbrains.annotations.Nullable;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
@@ -79,7 +78,7 @@ public class LodDimensionFileHandler
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read.
*/
public static final int LOD_SAVE_FILE_VERSION = 8;
public static final int LOD_SAVE_FILE_VERSION = 7;
/**
* Allow saving asynchronously, but never try to save multiple regions
@@ -101,8 +100,6 @@ public class LodDimensionFileHandler
//================//
// read from file //
//================//
@@ -119,65 +116,130 @@ public class LodDimensionFileHandler
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
{
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, generationMode, verticalQuality);
if (file == null) continue; // Failed to find the file for this detail level. continue and try next one
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
long fileSize = file.length();
if (fileSize == 0) continue; // file is empty. Let's not try parsing empty files
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
try
{
int fileVersion;
fileVersion = inputStream.read();
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
// check if this file can be read by this file handler
if (fileVersion < 6)
File file = new File(fileName);
if (!file.exists())
{
// the file we are reading is an older version,
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File has been deleted.");
// This should not break, but be continue to see whether other detail levels can be loaded or updated
continue;
//there is no file for current gen mode
//search others above current from the most to the least detailed
VerticalQuality tempVerticalQuality = VerticalQuality.HIGH;
do {
DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL;
do {
fileName = getFileNameAndPathForRegion(regionX, regionZ, tempGenMode, tempDetailLevel, verticalQuality);
if (fileName != null)
{
file = new File(fileName);
if (file.exists())
break;
}
//decrease gen mode
if (tempGenMode == DistanceGenerationMode.FULL)
tempGenMode = DistanceGenerationMode.FEATURES;
else if (tempGenMode == DistanceGenerationMode.FEATURES)
tempGenMode = DistanceGenerationMode.SURFACE;
else if (tempGenMode == DistanceGenerationMode.SURFACE)
tempGenMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT)
tempGenMode = DistanceGenerationMode.BIOME_ONLY;
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY)
tempGenMode = DistanceGenerationMode.NONE;
} while (tempGenMode != generationMode);
if (fileName != null)
{
file = new File(fileName);
if (file.exists())
break;
}
if (tempVerticalQuality == VerticalQuality.HIGH)
tempVerticalQuality = VerticalQuality.MEDIUM;
else if (tempVerticalQuality == VerticalQuality.MEDIUM)
tempVerticalQuality = VerticalQuality.LOW;
} while (tempVerticalQuality != verticalQuality);
if (!file.exists())
//there wasn't a file, don't return anything
continue;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
// don't try parsing empty files
long dataSize = file.length();
dataSize -= 1;
if (dataSize > 0)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
// This should not break, but be continue to see whether other detail levels can be loaded or updated
continue;
}
else if (fileVersion < LOD_SAVE_FILE_VERSION)
{
ClientApi.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File will be loaded and updated to new format in next save.");
// this is old, but readable version
// read and add the data to our region
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), fileVersion));
inputStream.close();
} else
{
// this file is a readable version,
// read and add the data to our region
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), LOD_SAVE_FILE_VERSION));
inputStream.close();
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
{
int fileVersion;
fileVersion = inputStream.read();
// check if this file can be read by this file handler
if (fileVersion < 6)
{
// the file we are reading is an older version,
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File was been deleted.");
break;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
break;
}
else if (fileVersion == 6)
{
//this is old, but readable version
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
inputStream.read(data);
inputStream.close();
// add the data to our region
region.addLevelContainer(new VerticalLevelContainer(data, 6));
} else
{
// this file is a readable version,
// read the file
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
inputStream.read(data);
inputStream.close();
// add the data to our region
region.addLevelContainer(new VerticalLevelContainer(data, LOD_SAVE_FILE_VERSION));
}
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
}
catch (IOException ioEx)
catch (Exception e)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
// the buffered reader encountered a
// problem reading the file
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}// for each detail level
@@ -232,111 +294,163 @@ public class LodDimensionFileHandler
{
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
// Get the old file
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
boolean isFileFullyGened = false;
// make sure the file and folder exists
if (!oldFile.exists())
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
{
// the file doesn't exist,
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
try {
ClientApi.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible.");
return;
}
File oldFile = new File(fileName);
//ClientProxy.LOGGER.info("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
byte[] temp = region.getLevel(detailLevel).toDataString();
try
{
// make sure the file and folder exists
if (!oldFile.exists())
{
// the file doesn't exist,
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
oldFile.createNewFile();
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
}
else
{
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
else
{
fileVersion = inputStream.read();
inputStream.skip(1);
isFileFullyGened = (inputStream.read() & 0b10000000) != 0;
inputStream.close();
}
catch (IOException e)
{
ClientApi.LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
e.printStackTrace();
}
// check if this file can be written to by the file handler
if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidentally
// delete anything the user may want.
continue;
}
// if we got this far then we are good
// to overwrite the old file
}
// Now create a new temporary save file
File tempFile;
try {
tempFile = File.createTempFile(oldFile.getName(), TMP_FILE_EXTENSION);
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create temp file for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
tempFile.deleteOnExit(); // Mark it to be deleted on exit if any unexcepted terminations happen
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(tempFile), 3))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
boolean isNewDataFullyGened = region.getLevel(detailLevel).writeData(new DataOutputStream(outputStream));
outputStream.close();
if (!isNewDataFullyGened && isFileFullyGened)
{
// existing file is complete while new one is only partially generate
// this can happen is for some reason loading failed
// this doesn't fix the bug, but at least protects old data
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + oldFile + "]");
try {
tempFile.delete();
} catch (SecurityException e) {
// Failed to delete temp file... just continue.
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
int isFull = 0;
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
inputStream.skip(1);
isFull = inputStream.read() & 0b10000000;
inputStream.close();
}
continue;
catch (IOException ex)
{
ex.printStackTrace();
}
// check if this file can be written to by the file handler
if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidentally
// delete anything the user may want.
return;
}
if ((temp[1] & 0b10000000) != 0b10000000 && isFull == 0b10000000)
{
// existing file is complete while new one is only partially generate
// this can happen is for some reason loading failed
// this doesn't fix the bug, but at least protects old data
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + fileName + "]");
return;
}
// if we got this far then we are good
// to overwrite the old file
}
// the old file is good, now create a new temporary save file
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
outputStream.write(temp);
outputStream.close();
// overwrite the old file with the new one
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
catch (IOException e)
catch (Exception e)
{
ClientApi.LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
// overwrite the old file with the new one
try {
Files.move(tempFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to update file [" + oldFile + "] error [" + e.getMessage() + "]: ");
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
}
public void saveRegionFile (byte[] regionFile, RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
if (fileName != null)
{
File oldFile = new File(fileName);
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (OutputStream os = new FileOutputStream(newFile))
{
os.write(regionFile);
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
os.close();
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
}
public byte[] getRegionFile (RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
if (fileName != null)
{
File file = new File(fileName);
try (InputStream is = new FileInputStream(file))
{
byte[] data = ThreadMapUtil.getSaveContainer(detailLevel);
is.read(data);
is.close();
return Arrays.copyOf(data, (int) file.length());
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
return new byte[0];
}
//================//
// helper methods //
//================//
public int getHashFromFile(RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
if (fileName == null)
return 0;
File file = new File(fileName);
return file.hashCode();
}
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
@@ -346,40 +460,26 @@ public class LodDimensionFileHandler
* <p>
* Returns null if there is an IO or security Exception.
*/
private String getFileBasePath() {
try {
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
} catch (IOException e) {
ClientApi.LOGGER.warn("Unable to get the base save file path. One possible cause is that"
+ " the process failed to read the current path location due to security configs.");
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data\"
// or
// ".\Super Flat\data\"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
verticalQuality + File.separatorChar +
generationMode.toString() + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch (IOException | SecurityException e)
{
ClientApi.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
e.printStackTrace();
return null;
}
}
private File getRegionFile(int regionX, int regionZ, DistanceGenerationMode genMode, byte detail, VerticalQuality vertQuality) {
return new File(getFileBasePath() + vertQuality + File.separatorChar +
genMode + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detail + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION);
}
// Return null if no file found
@Nullable
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, DistanceGenerationMode targetGenMode, VerticalQuality targetVertQuality) {
DistanceGenerationMode genMode = targetGenMode;
// Search from least GenMode to max GenMode, than least vertQuality to max vertQuality
do {
File file = getRegionFile(regionX, regionZ, genMode, detailLevel, targetVertQuality);
if (file.exists()) return file; // Found target file.
targetGenMode = DistanceGenerationMode.next(targetGenMode);
if (targetGenMode == null) { // Failed to find any files for this vertQuality. Try next one up.
targetGenMode = genMode;
targetVertQuality = VerticalQuality.next(targetVertQuality);
}
} while (targetVertQuality != null);
return null;
}
}
@@ -26,28 +26,26 @@ import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
/**
* A singleton used to get variables from methods
* where they are private or potentially absent.
* For example: the fog setting in Optifine or the
* presence/absence of Vivecraft.
* Specifically the fog setting in Optifine or the
* presence/absence of other mods.
*
* @author James Seibel
* @version 12-14-2021
* @version 11-26-2021
*/
public class ReflectionHandler implements IReflectionHandler
{
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName());
public static ReflectionHandler instance;
private static ReflectionHandler instance;
private Field ofFogField = null;
private final Object mcOptionsObject;
private Boolean sodiumPresent = null;
private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject)
@@ -77,8 +75,6 @@ public class ReflectionHandler implements IReflectionHandler
/** finds the Optifine fog type field */
private void setupFogField(Field[] optionFields)
{
@@ -159,29 +155,41 @@ public class ReflectionHandler implements IReflectionHandler
return false;
}
/**
* Modifies the projection matrix's clip planes.
* The projection matrix must be in column-major format.
*
* @param projectionMatrix The projection matrix to be modified.
* @param newNearClipPlane the new near clip plane value.
* @param newFarClipPlane the new far clip plane value.
* @return The modified matrix.
*/
@Override
public boolean sodiumPresent()
public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane)
{
// we don't want to run a potentially expensive
// reflection search operation every time this method is called
if (sodiumPresent == null)
{
try
{
Class.forName("me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer");
sodiumPresent = true;
}
catch (ClassNotFoundException e)
{
sodiumPresent = false;
}
}
// find the matrix values.
float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
return sodiumPresent;
try
{
// TODO this was originally created before we had the Mat4f object,
// so this doesn't need to be done with reflection anymore.
// And should be moved to RenderUtil
// get the fields of the projectionMatrix
Field[] fields = projectionMatrix.getClass().getDeclaredFields();
// bypass the security protections on the fields that encode near and far plane values.
fields[10].setAccessible(true);
fields[11].setAccessible(true);
// Change the values of the near and far plane.
fields[10].set(projectionMatrix, nearMatrixValue);
fields[11].set(projectionMatrix, farMatrixValue);
}
catch (Exception e)
{
e.printStackTrace();
}
return projectionMatrix;
}
}
@@ -200,9 +200,6 @@ public class VertexOptimizer
public final Map<LodDirection, byte[]> skyLights;
public byte blockLight;
boolean skipTop;
boolean skipBot;
/** creates an empty box */
@@ -320,13 +317,15 @@ public class VertexOptimizer
int maxY = getMaxY();
long singleAdjDataPoint;
// TODO transparency uncomment final condition to see ocean floor
/* TODO implement attached vertical face culling
//Up direction case
singleAdjDataPoint = adjData.get(LodDirection.UP)[0];
skipTop = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getDepth(singleAdjDataPoint) == maxY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
{
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
}*/
//Down direction case
singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0];
skipBot = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getHeight(singleAdjDataPoint) == minY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
if(DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
@@ -352,22 +351,17 @@ public class VertexOptimizer
boolean toFinish = false;
int toFinishIndex = 0;
boolean allAbove = true;
// TODO transparency ocean floor fix
//boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
// TODO transparency ocean floor fix
//if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255)
// continue;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth < maxY)
if (depth <= maxY)
{
allAbove = false;
if (height < minY)
@@ -421,7 +415,7 @@ public class VertexOptimizer
}
break;
}
else if (height >= maxY)// && depth > minY
else if (height >= maxY)//depth > minY &&
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
@@ -494,14 +488,13 @@ public class VertexOptimizer
boxOffset[Z] = zOffset;
}
/** returns true if the given direction should be rendered. */
/**
* returns true if the given direction should be rendered.
*/
public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
{
if (lodDirection == LodDirection.UP)
return adjIndex == 0 && !skipTop;
if (lodDirection == LodDirection.DOWN)
return adjIndex == 0 && !skipBot;
if (lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
return adjIndex == 0;
return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE);
}
@@ -19,10 +19,6 @@
package com.seibel.lod.core.objects.lod;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A level container is a quad tree level
*/
@@ -85,7 +81,7 @@ public interface LevelContainer
byte getDetailLevel();
int getVerticalSize();
int getMaxVerticalData();
/** Clears the dataPoint at the given array index */
void clear(int posX, int posZ);
@@ -103,17 +99,19 @@ public interface LevelContainer
* @param posZ z position in the detail level to update
*/
void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
/**
* This will write the raw data with metadata to the output stream
* @return isAllGenerated whether the data is all generated
* @throws IOException
* This will give the data to save in the file
* @return data as a String
*/
boolean writeData(DataOutputStream output) throws IOException;
byte[] toDataString();
/**
* This will give the data to save in the file
* @return data as a String
*/
int getMaxNumberOfLods();
void mergeMultiData(int posX, int posZ, long[] dataToMergeVertical, int inputVerticalData);
}
@@ -310,32 +310,6 @@ public class LodDimension
regions[xIndex][zIndex] = newRegion;
}
public interface PosComsumer {
void run(int x, int z);
}
public void iterateWithSpiral(PosComsumer r) {
int ox,oy,dx,dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.length;
int maxI = len*len;
int halfLen = len/2;
for(int i =0; i < maxI; i++){
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
int x = ox+halfLen;
int z = oy+halfLen;
r.run(x, z);
}
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
int temp = dx;
dx = -dy;
dy = temp;
}
ox += dx;
oy += dy;
}
}
/**
@@ -351,99 +325,116 @@ public class LodDimension
// don't run the tree cutter multiple times
// for the same location
if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ()) {
if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ())
{
lastCutChunk = newPlayerChunk;
Runnable thread = () -> {
Thread thread = new Thread(() ->
{
int regionX;
int regionZ;
int minDistance;
byte detail;
byte minAllowedDetailLevel;
// go over every region in the dimension
iterateWithSpiral((int x, int z) -> {
int regionX;
int regionZ;
int minDistance;
byte detail;
byte minAllowedDetailLevel;
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
if (regions[x][z] != null) {
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ,
playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) {
regions[x][z].cutTree(minAllowedDetailLevel);
recreateRegionBuffer[x][z] = true;
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
if (regions[x][z] != null)
{
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel)
{
regions[x][z].cutTree(minAllowedDetailLevel);
recreateRegionBuffer[x][z] = true;
}
}
}
});
};
}// region z
}// region z
});
cutAndExpandThread.execute(thread);
}
}
/** Either expands or loads all regions in the rendered LOD area */
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ) {
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ)
{
DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX),
LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality();
if (lastExpandedChunk == null)
lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
// don't run the expander multiple times
// for the same location
if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ()) {
if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ())
{
lastExpandedChunk = newPlayerChunk;
Runnable thread = () -> {
iterateWithSpiral((int x, int z) -> {
int regionX;
int regionZ;
LodRegion region;
int minDistance;
byte detail;
byte levelToGen;
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
playerPosZ);
detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
// check that the region isn't null and at least this detail level
if (region == null || region.getGenerationMode() != generationMode) {
// First case, region has to be created
// try to get the region from file
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
// if there is no region file create an empty region
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
regenRegionBuffer[x][z] = true;
regenDimensionBuffers = true;
recreateRegionBuffer[x][z] = true;
} else if (region.getMinDetailLevel() > levelToGen) {
// Second case, the region exists at a higher detail level.
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
recreateRegionBuffer[x][z] = true;
Thread thread = new Thread(() ->
{
int regionX;
int regionZ;
LodRegion region;
int minDistance;
byte detail;
byte levelToGen;
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
// check that the region isn't null and at least this detail level
if (region == null || region.getGenerationMode() != generationMode)
{
// First case, region has to be created
// try to get the region from file
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
// if there is no region file create an empty region
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
regenRegionBuffer[x][z] = true;
regenDimensionBuffers = true;
recreateRegionBuffer[x][z] = true;
}
else if (region.getMinDetailLevel() > levelToGen)
{
// Second case, the region exists at a higher detail level.
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
recreateRegionBuffer[x][z] = true;
}
}
});
};
}
});
cutAndExpandThread.execute(thread);
}
}
@@ -690,10 +681,7 @@ public class LodDimension
LodRegion region = getRegion(regionPos.x, regionPos.z);
// use FAR_FIRST on local worlds and NEAR_FIRST on servers
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority();
if (generationPriority == GenerationPriority.AUTO)
generationPriority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO && MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST;
if (region != null)
@@ -921,4 +909,39 @@ public class LodDimension
{
isRegionDirty[i][j] = val;
}
public void mergeMultiData(byte detailLevel, int posX, int posZ, boolean dontSave, long[] dataToMergeVertical, int inputVerticalData)
{
int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
// don't continue if the region can't be saved
LodRegion region = getRegion(regionPosX, regionPosZ);
if (region == null)
return;
region.mergeMultiData(detailLevel, posX, posZ, dataToMergeVertical, inputVerticalData);
// only save valid LODs to disk
if (!dontSave && fileHandler != null)
{
try
{
// mark the region as dirty, so it will be saved to disk
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
regenRegionBuffer[xIndex][zIndex] = true;
regenDimensionBuffers = true;
}
catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
// If this happens, the method was probably
// called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
}
}
}
@@ -385,7 +385,7 @@ public class LodRegion
{
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
if (!requireCorrectDetailLevel)
if (requireCorrectDetailLevel)
childrenCount++;
else
getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
@@ -394,7 +394,7 @@ public class LodRegion
}
if (!requireCorrectDetailLevel)
if (requireCorrectDetailLevel)
{
// If all the four children exist go deeper
if (childrenCount == 4)
@@ -601,7 +601,7 @@ public class LodRegion
public int getMaxVerticalData(byte detailLevel)
{
return dataContainer[detailLevel].getVerticalSize();
return dataContainer[detailLevel].getMaxVerticalData();
}
@@ -610,4 +610,16 @@ public class LodRegion
{
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
}
public void mergeMultiData(byte detailLevel, int posX, int posZ, long[] dataToMergeVertical, int inputVerticalData)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
this.dataContainer[detailLevel].mergeMultiData(posX, posZ, dataToMergeVertical, inputVerticalData);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,13 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class LodQuadTree
{
public LodSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public LodQuadTree()
{
}
}
@@ -1,115 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
/**
A lod section rappresent a distinct section in a precise level of the quadtree.
The section holds all the lods information as color (computed with the blockBiome object referenced by the ID), size, light...
The save and load of a section is handled by the section itself together with a handler class.
*/
public class LodSection
{
//level of detail of this section
public final int detail;
//level position of this section
public final int sectionPosX;
public final int sectionPosZ;
//horizontal size of this section
public final int horizontalSize;
//vertical size of this section
public final int verticalSize;
//how many id we save for each lod
public final int idPerLod;
//What generation mode should be used for chunk in this section
public final byte generationMode;
//if present in region file, use pregenerated chunk in lod creation
public boolean usePregeneratedChunk;
//Position data hold information about each level position like generation mode used, number of vertical lods in that area...
private byte[] positionData;
//Position data hold vertical information about each lod, like minY and maxY...
private int[] verticalData;
//Lights data hold lights information about each lod, like skylight and block light values...
private byte[] lightsData;
//BlockBiomeId hold a unique ID for each lod relative to a block-biome couple. This couple is capable of generating color
//We could just reference
private int[] BlockBiomeId;
//BlockBiomeFrequency for each lod BlockBiomeId
private byte[] BlockBiomeFrequency;
/**
*
* @param detail
* @param horizontalSize
* @param verticalSize
* @param levelPosX
* @param levelPosZ
* @param generationMode
* @param avoidPregeneratedChunk
* @param idPerLod
*/
public LodSection(int detail, int horizontalSize, int verticalSize, int levelPosX, int levelPosZ, byte generationMode, boolean avoidPregeneratedChunk, int idPerLod)
{
this.detail = detail;
this.sectionPosX = levelPosX;
this.sectionPosZ = levelPosZ;
this.horizontalSize = horizontalSize;
this.verticalSize = verticalSize;
this.generationMode = generationMode;
this.usePregeneratedChunk = avoidPregeneratedChunk;
this.idPerLod = idPerLod;
//Now we should search if there is a file with this values
//if so we load it and end the process
//Otherwise we initialize this section
positionData = new byte[horizontalSize*horizontalSize];
verticalData = new int[horizontalSize*horizontalSize*verticalSize];
lightsData = new byte[horizontalSize*horizontalSize*verticalSize];
BlockBiomeId = new int[horizontalSize*horizontalSize*verticalSize*idPerLod];
BlockBiomeFrequency = new byte[horizontalSize*horizontalSize*verticalSize*idPerLod];
}
/**
*
* @param otherSection
* @param inputStartX
* @param inputStartZ
* @param inputEndX
* @param inputEndZ
* @param targetStartX
* @param targetStartZ
* @param targetEndX
* @param targetEndZ
*/
public void mergeAndAdd(LodSection otherSection,
int inputStartX, int inputStartZ, int inputEndX, int inputEndZ,
int targetStartX, int targetStartZ, int targetEndX, int targetEndZ)
{
}
public short getPositionData(int posX, int posZ)
{
return 0;
}
public long[] getData(int posX, int posZ)
{
return null;
}
public void save()
{
}
public void tryload()
{
}
}
@@ -1,11 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeMover
{
public static void move(LodQuadTree lqt, RenderQuadTree rqt, int centerX, int centerZ)
{
}
}
@@ -1,36 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeProperties
{
public int MAX_NUMBER_OF_DETAIL=23;
public int SECTION_SIZE=128;
public int[] absoluteDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] relativeDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] generationModeOfDetail = new int[MAX_NUMBER_OF_DETAIL];
public LodQuadTree lodQuadTree;
public RenderQuadTree renderQuadTree;
public void update()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Compute circle distance for this detail in term of blocks
absoluteDetailCircleSize[detail] = DetailDistanceUtil.getDrawDistanceFromDetail(detail);
//Compute circle distance in terms of number of section
relativeDetailCircleSize[detail] = (int) Math.ceil(absoluteDetailCircleSize[detail]/(SECTION_SIZE*2^detail));
}
updateGridSize();
}
private void updateGridSize()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Use this value to change to update the size of the matrices
//relativeDetailCircleSize[detail];
}
}
}
@@ -1,13 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class RenderQuadTree
{
//public RenderSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public RenderQuadTree()
{
}
}
@@ -37,7 +37,7 @@ import com.google.common.collect.Lists;
* OpenGL buffers.
*
* @author James Seibel
* @version 12-9-2021
* @version 12-8-2021
*/
public class LodBufferBuilder
{
@@ -83,7 +83,7 @@ public class LodBufferBuilder
/** make sure the buffer doesn't overflow when inserting new elements */
private void ensureVertexCapacity()
{
this.ensureCapacity(this.format.getByteSize());
this.ensureCapacity(this.format.getVertexSize());
}
private void ensureCapacity(int vertexSizeInBytes)
{
@@ -282,7 +282,7 @@ public class LodBufferBuilder
{
this.building = false;
this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode));
this.totalRenderedBytes += this.vertices * this.format.getByteSize();
this.totalRenderedBytes += this.vertices * this.format.getVertexSize();
this.vertices = 0;
this.currentElement = null;
this.elementIndex = 0;
@@ -450,7 +450,7 @@ public class LodBufferBuilder
{
LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
this.buffer.position(this.totalUploadedBytes);
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize();
this.buffer.limit(this.totalUploadedBytes);
if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
{
@@ -458,7 +458,7 @@ public class LodBufferBuilder
}
ByteBuffer bytebuffer = this.buffer.slice();
//bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
this.buffer.clear();
return bytebuffer; // the original method also returned bufferbuilder$drawstate
}
@@ -543,10 +543,10 @@ public class LodBufferBuilder
// Forge added methods
public void putBulkData(ByteBuffer buffer)
{
ensureCapacity(buffer.limit() + this.format.getByteSize());
this.buffer.position(this.vertices * this.format.getByteSize());
ensureCapacity(buffer.limit() + this.format.getVertexSize());
this.buffer.position(this.vertices * this.format.getVertexSize());
this.buffer.put(buffer);
this.vertices += buffer.limit() / this.format.getByteSize();
this.vertices += buffer.limit() / this.format.getVertexSize();
this.nextElementByte += buffer.limit();
}
@@ -19,7 +19,7 @@
package com.seibel.lod.core.objects.opengl;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL15;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
@@ -41,7 +41,7 @@ public class LodVertexBuffer implements AutoCloseable
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
this.id = GL32.glGenBuffers();
this.id = GL15.glGenBuffers();
}
@@ -50,7 +50,7 @@ public class LodVertexBuffer implements AutoCloseable
{
if (this.id >= 0)
{
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(this.id));
GLProxy.getInstance().recordOpenGlCall(() -> GL15.glDeleteBuffers(this.id));
this.id = -1;
}
}
@@ -35,13 +35,13 @@ import it.unimi.dsi.fastutil.ints.IntList;
* were commented out since we didn't need them.
*
* @author James Seibel
* @version 12-9-2021
* @version 11-13-2021
*/
public class LodVertexFormat
{
private final ImmutableList<LodVertexFormatElement> elements;
private final IntList offsets = new IntArrayList();
private final int byteSize;
private final int vertexSize;
public LodVertexFormat(ImmutableList<LodVertexFormatElement> elementList)
{
@@ -54,12 +54,17 @@ public class LodVertexFormat
i += LodVertexFormatElement.getByteSize();
}
this.byteSize = i;
this.vertexSize = i;
}
public int getByteSize()
public int getIntegerSize()
{
return this.byteSize;
return this.getVertexSize() / 4;
}
public int getVertexSize()
{
return this.vertexSize;
}
public ImmutableList<LodVertexFormatElement> getElements()
@@ -93,7 +98,7 @@ public class LodVertexFormat
else if (obj != null && this.getClass() == obj.getClass())
{
LodVertexFormat vertexformat = (LodVertexFormat) obj;
return this.byteSize == vertexformat.byteSize && this.elements.equals(vertexformat.elements);
return this.vertexSize == vertexformat.vertexSize && this.elements.equals(vertexformat.elements);
}
else
{
@@ -0,0 +1,43 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.rending;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer.
*
* @author James Seibel
* @version 11-26-2021
*/
public class LodFogConfig
{
public FogDrawMode fogDrawMode;
public FogDistance fogDistance;
public float nearFogStart = 0;
public float nearFogEnd = 0;
public float farFogStart = 0;
public float farFogEnd = 0;
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,26 +19,25 @@
package com.seibel.lod.core.render;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.GLUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.shader.LodShader;
import com.seibel.lod.core.render.shader.LodShaderProgram;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -58,16 +57,15 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
*
* @author James Seibel
* @version 12-9-2021
* @version 12-8-2021
*/
public class GLProxy
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static GLProxy instance = null;
@@ -86,72 +84,22 @@ public class GLProxy
/** the proxyWorker's GL capabilities */
public final GLCapabilities proxyWorkerGlCapabilities;
/** Requires OpenGL 4.4, and offers the best buffer uploading */
/** This program contains all shaders required when rendering LODs */
public LodShaderProgram lodShaderProgram;
/** This is the VAO that is used when rendering */
public final int vertexArrayObjectId;
/** This is the 2D texture that holds MC's lightmap */
public final int lightMapTextureId;
/** Requires OpenGL 4.5, and offers the best buffer uploading */
public final boolean bufferStorageSupported;
/** Requires OpenGL 4.5 */
public final boolean namedObjectSupported;
/** Requires OpenGL 4.3 */
public final boolean VertexAttributeBufferBindingSupported;
/** Requires OpenGL 3.0 */
public final boolean mapBufferRangeSupported;
/** Requires OpenGL 3.0, which will current min requirement as 3.3, should always be true */
@Deprecated
public final boolean mapBufferRangeSupported = true;
private final GpuUploadMethod preferredUploadMethod;
private String getFailedVersionInfo(GLCapabilities c) {
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
str.append("1.1: "+c.OpenGL11+"\n");
str.append("1.2: "+c.OpenGL12+"\n");
str.append("1.3: "+c.OpenGL13+"\n");
str.append("1.4: "+c.OpenGL14+"\n");
str.append("1.5: "+c.OpenGL15+"\n");
str.append("2.0: "+c.OpenGL20+"\n");
str.append("2.1: "+c.OpenGL21+"\n");
str.append("3.0: "+c.OpenGL30+"\n");
str.append("3.1: "+c.OpenGL31+"\n");
str.append("3.2: "+c.OpenGL32+" <- REQUIRED\n");
str.append("3.3: "+c.OpenGL33+"\n");
str.append("4.0: "+c.OpenGL40+"\n");
str.append("4.1: "+c.OpenGL41+"\n");
str.append("4.2: "+c.OpenGL42+"\n");
str.append("4.3: "+c.OpenGL43+" <- optional improvement\n");
str.append("4.4: "+c.OpenGL44+" <- optional improvement\n");
str.append("4.5: "+c.OpenGL45+"\n");
str.append("4.6: "+c.OpenGL46+"\n");
str.append("If you noticed that your computer supports higher OpenGL versions"
+ " but not the required version, try running the game in compatibility mode."
+ " (How you turn that on, I have no clue~)");
return str.toString();
}
private String getVersionInfo(GLCapabilities c) {
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
str.append("1.1: "+c.OpenGL11+"\n");
str.append("1.2: "+c.OpenGL12+"\n");
str.append("1.3: "+c.OpenGL13+"\n");
str.append("1.4: "+c.OpenGL14+"\n");
str.append("1.5: "+c.OpenGL15+"\n");
str.append("2.0: "+c.OpenGL20+"\n");
str.append("2.1: "+c.OpenGL21+"\n");
str.append("3.0: "+c.OpenGL30+"\n");
str.append("3.1: "+c.OpenGL31+"\n");
str.append("3.2: "+c.OpenGL32+" <- REQUIRED\n");
str.append("3.3: "+c.OpenGL33+"\n");
str.append("4.0: "+c.OpenGL40+"\n");
str.append("4.1: "+c.OpenGL41+"\n");
str.append("4.2: "+c.OpenGL42+"\n");
str.append("4.3: "+c.OpenGL43+" <- optional improvement\n");
str.append("4.4: "+c.OpenGL44+" <- optional improvement\n");
str.append("4.5: "+c.OpenGL45+"\n");
str.append("4.6: "+c.OpenGL46+"\n");
return str.toString();
}
/**
@@ -161,14 +109,7 @@ public class GLProxy
*/
private GLProxy()
{
boolean enableDebugLogging = CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL;
// this must be created on minecraft's render context to work correctly
ClientApi.LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
// getting Minecraft's context has to be done on the render thread,
// where the GL context is
@@ -182,20 +123,7 @@ public class GLProxy
// get Minecraft's context
minecraftGlContext = GLFW.glfwGetCurrentContext();
minecraftGlCapabilities = GL.getCapabilities();
// crash the game if the GPU doesn't support OpenGL 3.2
if (!minecraftGlCapabilities.OpenGL32)
{
String supportedVersionInfo = getFailedVersionInfo(minecraftGlCapabilities);
// Note: as of MC 1.17 this shouldn't happen since MC
// requires OpenGL 3.2, but for older MC version this will warn the player.
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName()
+ " and discovered this GPU doesn't support OpenGL 3.2." + " Sorry I couldn't tell you sooner :(\n"+
"Additional info:\n"+supportedVersionInfo;
MC.crashMinecraft(errorMessage, new UnsupportedOperationException("This GPU doesn't support OpenGL 3.2."));
}
ClientApi.LOGGER.info("minecraftGlCapabilities:\n"+getVersionInfo(minecraftGlCapabilities));
// context creation setup
GLFW.glfwDefaultWindowHints();
@@ -206,24 +134,21 @@ public class GLProxy
// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4);
// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 5);
// create the LodBuilder context
lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, minecraftGlContext);
GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
lodBuilderGlCapabilities = GL.createCapabilities();
ClientApi.LOGGER.info("lodBuilderGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
// create the proxyWorker's context
proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext);
GLFW.glfwMakeContextCurrent(proxyWorkerGlContext);
proxyWorkerGlCapabilities = GL.createCapabilities();
ClientApi.LOGGER.info("proxyWorkerGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
// Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after
VertexAttributeBufferBindingSupported = minecraftGlCapabilities.glBindVertexBuffer != 0L; // Nullptr
// UNUSED currently
// Check if we can use the named version of all calls, which is available in GL4.5 or after
namedObjectSupported = minecraftGlCapabilities.glNamedBufferData != 0L; //Nullptr
//==================================//
@@ -231,62 +156,88 @@ public class GLProxy
//==================================//
setGlContext(GLProxyContext.LOD_BUILDER);
// TODO: Enable this but disable INFO logging
File proxyLog = new File("OpenGL-Lod-ProxyContext.log");
try {
proxyLog.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
// crash the game if the GPU doesn't support OpenGL 2.0
if (!minecraftGlCapabilities.OpenGL20)
{
// Note: as of MC 1.17 this shouldn't happen since MC
// requires OpenGL 3.3, but just in case.
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 2.0 or greater.";
MC.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 2.0 or greater."));
}
if (enableDebugLogging)
try {
GLUtil.setupDebugMessageCallback(new PrintStream(proxyLog));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// get specific capabilities
// Check if we can use the Buffer Storage, which is available in GL4.4 or after
bufferStorageSupported = minecraftGlCapabilities.glBufferStorage != 0L && lodBuilderGlCapabilities.glBufferStorage != 0L; // Nullptr
//bufferStorageSupported = true;
bufferStorageSupported = lodBuilderGlCapabilities.glBufferStorage != 0;
mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0;
// display the capabilities
if (!bufferStorageSupported)
{
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5";
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.5), falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance.");
}
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
{
// NVIDIA card
preferredUploadMethod = bufferStorageSupported ? GpuUploadMethod.BUFFER_STORAGE : GpuUploadMethod.SUB_DATA;
}
else
{
// AMD or Intel card
preferredUploadMethod = GpuUploadMethod.BUFFER_MAPPING;
}
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + preferredUploadMethod + "].");
setGlContext(GLProxyContext.PROXY_WORKER);
File workerLog = new File("OpenGL-Lod-WorkerContext.log");
try {
workerLog.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (enableDebugLogging)
try {
GLUtil.setupDebugMessageCallback(new PrintStream(workerLog));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// if using AUTO gpuUpload
// determine a good default for the GPU
if (CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.AUTO)
{
GpuUploadMethod uploadMethod;
String vendor = GL15.glGetString(GL15.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
{
// NVIDIA card
if (bufferStorageSupported)
{
uploadMethod = GpuUploadMethod.BUFFER_STORAGE;
}
else
{
uploadMethod = GpuUploadMethod.SUB_DATA;
}
}
else
{
// AMD or Intel card
if (mapBufferRangeSupported)
{
uploadMethod = GpuUploadMethod.BUFFER_MAPPING;
}
else
{
uploadMethod = GpuUploadMethod.DATA;
}
}
CONFIG.client().advanced().buffers().setGpuUploadMethod(uploadMethod);
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Upload method set to [" + uploadMethod + "].");
}
//==============//
// shader setup //
//==============//
setGlContext(GLProxyContext.MINECRAFT);
createShaderProgram();
// Note: VAO objects can not be shared between contexts,
// this must be created on minecraft's render context to work correctly
vertexArrayObjectId = GL30.glGenVertexArrays();
lightMapTextureId = GL30.glGenTextures();
//==========//
// clean up //
@@ -295,10 +246,88 @@ public class GLProxy
// Since this is created on the render thread, make sure the Minecraft context is used in the end
setGlContext(GLProxyContext.MINECRAFT);
// GLProxy creation success
ClientApi.LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
}
/**
* Creates all required shaders
* @throws RuntimeException
* @throws FileNotFoundException
*/
public void createShaderProgram()
{
LodShader vertexShader = null;
LodShader fragmentShader = null;
try
{
// get the shaders from the resource folder
vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "shaders/standard.vert", false);
fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders/flat_shaded.frag", false);
// this can be used when testing shaders,
// since we can't hot swap the files in the resource folder
// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/standard.vert", true);
// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.frag", true);
// create the shaders
lodShaderProgram = new LodShaderProgram();
// Attach the compiled shaders to the program
lodShaderProgram.attachShader(vertexShader);
lodShaderProgram.attachShader(fragmentShader);
// activate the fragment shader output
GL30.glBindFragDataLocation(lodShaderProgram.id, 0, "fragColor");
// attach the shader program to the OpenGL context
lodShaderProgram.link();
// after the shaders have been attached to the program
// we don't need their OpenGL references anymore
GL20.glDeleteShader(vertexShader.id);
GL20.glDeleteShader(fragmentShader.id);
}
catch (Exception e)
{
ClientApi.LOGGER.error("Unable to compile shaders. Error: " + e.getMessage());
}
// get the shaders from the resource folder
// Use File.separator ONLY if the file is external (not in the resourse), otherwise use "/"
vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "shaders/standard.vert", false);
fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders/flat_shaded.frag", false);
// this can be used when testing shaders,
// since we can't hot swap the files in the resource folder
// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/standard.vert", true);
// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.frag", true);
// create the shaders
lodShaderProgram = new LodShaderProgram();
// Attach the compiled shaders to the program, throws RuntimeException on link error
lodShaderProgram.attachShader(vertexShader);
lodShaderProgram.attachShader(fragmentShader);
// activate the fragment shader output
GL30.glBindFragDataLocation(lodShaderProgram.id, 0, "fragColor");
// attach the shader program to the OpenGL context
lodShaderProgram.link();
// after the shaders have been attached to the program
// we don't need their OpenGL references anymore
GL20.glDeleteShader(vertexShader.id);
GL20.glDeleteShader(fragmentShader.id);
}
/**
* A wrapper function to make switching contexts easier. <br>
* Does nothing if the calling thread is already using newContext.
@@ -368,29 +397,21 @@ public class GLProxy
+ "no context [0].");
}
public static boolean hasInstance() {
return instance != null;
}
public static GLProxy getInstance()
{
if (instance == null)
instance = new GLProxy();
return instance;
}
public GpuUploadMethod getGpuUploadMethod() {
GpuUploadMethod method = CONFIG.client().advanced().buffers().getGpuUploadMethod();
if (!bufferStorageSupported && method == GpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to SUB_DATA
method = GpuUploadMethod.SUB_DATA;
}
return method == GpuUploadMethod.AUTO ? preferredUploadMethod : method;
}
/**
* Asynchronously calls the given runnable on proxy's OpenGL context.
@@ -431,7 +452,7 @@ public class GLProxy
* This only works with Legacy OpenGL because James hasn't
* looking into a way for it to work with Modern OpenGL.
*/
public boolean disableLegacyFog()
public void disableLegacyFog()
{
// make sure this is a legacy OpenGL context
if (minecraftGlCapabilities.glFogf != 0)
@@ -444,9 +465,7 @@ public class GLProxy
GL11.glFogf(GL11.GL_FOG_START, 0.0f);
GL11.glFogf(GL11.GL_FOG_END, Float.MAX_VALUE);
GL11.glFogf(GL11.GL_FOG_DENSITY, 0.0f);
return true;
}
return false;
}
@@ -1,75 +0,0 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer.
*
* @author James Seibel
* @version 11-26-2021
*/
public class LodFogConfig
{
public FogDrawMode fogDrawMode;
public FogDistance fogDistance;
public float nearFogStart = 0;
public float nearFogEnd = 0;
public float farFogStart = 0;
public float farFogEnd = 0;
public LodFogConfig(ILodConfigWrapperSingleton config, IReflectionHandler reflectionHandler, int farPlaneBlockDistance, int vanillaBlockRenderedDistance) {
fogDrawMode = config.client().graphics().fogQuality().getFogDrawMode();
if (fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
fogDrawMode = reflectionHandler.getFogDrawMode();
// how different distances are drawn depends on the quality set
fogDistance = config.client().graphics().fogQuality().getFogDistance();
// far fog //
if (config.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
farFogStart = farPlaneBlockDistance * 0.9f;
else
// for more realistic fog when using FAR
farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f);
farFogEnd = farPlaneBlockDistance;
// near fog //
// the reason that I wrote fogEnd then fogStart backwards
// is because we are using fog backwards to how
// it is normally used, hiding near objects
// instead of far objects.
nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
nearFogStart = vanillaBlockRenderedDistance * 1.6f;
}
}
@@ -1,155 +0,0 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render;
import java.awt.Color;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.render.objects.ShaderProgram;
import com.seibel.lod.core.render.objects.VertexAttribute;
import com.seibel.lod.core.render.objects.VertexAttributePostGL43;
import com.seibel.lod.core.render.objects.VertexAttributePreGL43;
import com.seibel.lod.core.util.LodUtil;
public class LodRenderProgram extends ShaderProgram {
public static final String VERTEX_SHADER_PATH = "shaders/standard.vert";
public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag";
public final VertexAttribute vao;
// Attributes
public final int posAttrib;
public final int colAttrib;
public final int blockSkyLightAttrib;
public final int blockLightAttrib;
// Uniforms
public final int mvmUniform;
public final int projUniform;
public final int cameraUniform;
public final int fogColorUniform;
// public final int skyLightUniform; worldSkyLight is currently not used
public final int lightMapUniform;
// Fog Uniforms
public final int fogEnabledUniform;
public final int nearFogEnabledUniform;
public final int farFogEnabledUniform;
public final int nearFogStartUniform;
public final int nearFogEndUniform;
public final int farFogStartUniform;
public final int farFogEndUniform;
// This will bind VertexAttribute
public LodRenderProgram() {
super(VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH, "fragColor");
posAttrib = getAttributeLocation("vPosition");
colAttrib = getAttributeLocation("color");
blockSkyLightAttrib = getAttributeLocation("blockSkyLight");
blockLightAttrib = getAttributeLocation("blockLight");
mvmUniform = getUniformLocation("modelViewMatrix");
projUniform = getUniformLocation("projectionMatrix");
cameraUniform = getUniformLocation("cameraPos");
fogColorUniform = getUniformLocation("fogColor");
// skyLightUniform = getUniformLocation("worldSkyLight");
lightMapUniform = getUniformLocation("lightMap");
// Fog uniforms
fogEnabledUniform = getUniformLocation("fogEnabled");
nearFogEnabledUniform = getUniformLocation("nearFogEnabled");
farFogEnabledUniform = getUniformLocation("farFogEnabled");
// near
nearFogStartUniform = getUniformLocation("nearFogStart");
nearFogEndUniform = getUniformLocation("nearFogEnd");
// far
farFogStartUniform = getUniformLocation("farFogStart");
farFogEndUniform = getUniformLocation("farFogEnd");
// TODO: Add better use of the LODFormat thing
int vertexByteCount = LodUtil.LOD_VERTEX_FORMAT.getByteSize();
if (GLProxy.getInstance().VertexAttributeBufferBindingSupported)
vao = new VertexAttributePostGL43(); // also binds VertexAttribute
else
vao = new VertexAttributePreGL43(); // also binds VertexAttribute
//vao.bind();
vao.setVertexAttribute(0, posAttrib, VertexAttribute.VertexPointer.addVec3Pointer(false));
vao.setVertexAttribute(0, colAttrib, VertexAttribute.VertexPointer.addUnsignedBytesPointer(4, true));
vao.setVertexAttribute(0, blockSkyLightAttrib, VertexAttribute.VertexPointer.addUnsignedBytePointer(false));
vao.setVertexAttribute(0, blockLightAttrib, VertexAttribute.VertexPointer.addUnsignedBytePointer(false));
vao.completeAndCheck(vertexByteCount);
}
// Override ShaderProgram.bind()
public void bind() {
super.bind();
vao.bind();
}
// Override ShaderProgram.unbind()
public void unbind() {
super.unbind();
vao.unbind();
}
// Override ShaderProgram.free()
public void free() {
vao.free();
super.free();
}
public void bindVertexBuffer(int vbo) {
vao.bindBufferToAllBindingPoint(vbo);
}
public void unbindVertexBuffer() {
vao.unbindBuffersFromAllBindingPoint();
}
public void fillUniformData(Mat4f modelViewMatrix, Mat4f projectionMatrix, Vec3f cameraPos, Color fogColor, int skyLight, int lightmapBindPoint) {
super.bind();
// uniforms
setUniform(mvmUniform, modelViewMatrix);
setUniform(projUniform, projectionMatrix);
setUniform(cameraUniform, cameraPos);
setUniform(fogColorUniform, fogColor);
// setUniform(skyLightUniform, skyLight);
setUniform(lightMapUniform, lightmapBindPoint);
}
public void fillUniformDataForFog(LodFogConfig fogSettings) {
super.bind();
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED) {
setUniform(fogEnabledUniform, true);
setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
// near
setUniform(nearFogStartUniform, fogSettings.nearFogStart);
setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
// far
setUniform(farFogStartUniform, fogSettings.farFogStart);
setUniform(farFogEndUniform, fogSettings.farFogEnd);
} else {
setUniform(fogEnabledUniform, false);
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,10 @@ package com.seibel.lod.core.render;
import java.awt.Color;
import java.util.HashSet;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
@@ -32,18 +35,21 @@ import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.FogColorMode;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.render.objects.LightmapTexture;
import com.seibel.lod.core.objects.rending.LodFogConfig;
import com.seibel.lod.core.render.shader.LodShaderProgram;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -54,7 +60,7 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
* This is where LODs are draw to the world.
*
* @author James Seibel
* @version 12-12-2021
* @version 12-8-2021
*/
public class LodRenderer
{
@@ -62,6 +68,8 @@ public class LodRenderer
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
/**
* If true the LODs colors will be replaced with
@@ -69,8 +77,8 @@ public class LodRenderer
*/
public DebugMode previousDebugMode = DebugMode.OFF;
// This tells us if the renderer is enabled or not. If in a world, it should be enabled.
private boolean isSetupComplete = false;
private int farPlaneBlockDistance;
/** This is used to generate the buildable buffers */
private final LodBufferBuilderFactory lodBufferBuilderFactory;
@@ -82,18 +90,19 @@ public class LodRenderer
* These have to be separate because we can't override the
* buffers in the VBOs (and we don't want to)
*/
private int[][][] storageBufferIds = null;
// The shader program
LodRenderProgram shaderProgram = null;
@SuppressWarnings("unused")
private int[][][] storageBufferIds;
private int vbosCenterX = 0;
private int vbosCenterZ = 0;
/** This is used to determine if the LODs should be regenerated */
private int[] previousPos = new int[] { 0, 0, 0 };
// these variables are used to determine if the buffers should be rebuilt
private float prevSkyBrightness = 0;
private double prevBrightness = 0;
private int prevRenderDistance = 0;
private long prevPlayerPosTime = 0;
private long prevVanillaChunkTime = 0;
@@ -109,7 +118,6 @@ public class LodRenderer
*/
private volatile boolean partialRegen = false;
private volatile boolean fullRegen = true;
private volatile boolean markToCleanup = false;
/**
* This HashSet contains every chunk that Vanilla Minecraft
@@ -118,16 +126,22 @@ public class LodRenderer
public boolean[][] vanillaRenderedChunks;
public boolean vanillaRenderedChunksChanged;
public boolean vanillaRenderedChunksEmptySkip = false;
public int vanillaBlockRenderedDistance;
private boolean canVanillaFogBeDisabled = true;
public void requestCleanup() {markToCleanup = true;}
public LodRenderer(LodBufferBuilderFactory newLodNodeBufferBuilder)
{
lodBufferBuilderFactory = newLodNodeBufferBuilder;
}
/**
* Besides drawing the LODs this method also starts
* the async process of generating the Buffers that hold those LODs.
@@ -149,8 +163,6 @@ public class LodRenderer
return;
}
if (MC_RENDER.playerHasBlindnessEffect())
{
// if the player is blind, don't render LODs,
@@ -158,25 +170,16 @@ public class LodRenderer
// which blindness relies on.
return;
}
// get MC's shader program
// Save all MC render state
int currentProgram = GL32.glGetInteger(GL32.GL_CURRENT_PROGRAM);
int currentVBO = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
int currentVAO = GL32.glGetInteger(GL32.GL_VERTEX_ARRAY_BINDING);
int currentActiveText = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
boolean currentBlend = GL32.glGetBoolean(GL32.GL_BLEND);
GLProxy glProxy = GLProxy.getInstance();
if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
if (!glProxy.disableLegacyFog())
if (!MC_RENDER.tryDisableVanillaFog())
canVanillaFogBeDisabled = false;
if (CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
GLProxy.getInstance().disableLegacyFog();
// TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
// starting here...
determineIfLodsShouldRegenerate(lodDim, partialTicks);
//=================//
// create the LODs //
@@ -190,7 +193,7 @@ public class LodRenderer
if ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable())
{
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen);
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), true);
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvailable()
@@ -207,169 +210,353 @@ public class LodRenderer
swapBuffers();
}
if (vbos == null) {
// There is still no vbos, which means nothing needs to be drawn. So no rendering needed
// (Vbos should be setup by now)
return;
}
// FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
// The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
if (markToCleanup) {
markToCleanup = false;
cleanup(); // This will unset the isSetupComplete, causing a setup() call.
}
//===================//
// draw params setup //
//===================//
profiler.push("LOD draw setup");
/*---------Set GL State--------*/
// Make sure to unbind current VBO so we don't mess up vanilla settings
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
//===============//
// initial setup //
//===============//
profiler.push("LOD setup");
GLProxy glProxy = GLProxy.getInstance();
// set the required open GL settings
if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME)
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE);
else
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GL32.glEnable(GL32.GL_CULL_FACE);
GL32.glEnable(GL32.GL_DEPTH_TEST);
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
GL15.glEnable(GL15.GL_CULL_FACE);
GL15.glEnable(GL15.GL_DEPTH_TEST);
// enable transparent rendering
// GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
// GL32.glEnable(GL32.GL_BLEND);
GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA);
GL15.glEnable(GL15.GL_BLEND);
// get MC's shader program
int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM);
/*---------Bind required objects--------*/
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
// also binds LightmapTexture, VAO, and ShaderProgram
if (!isSetupComplete) {
setup();
} else {
shaderProgram.bind();
}
GL32.glActiveTexture(GL32.GL_TEXTURE0);
LightmapTexture lightmapTexture = new LightmapTexture();
/*---------Get required data--------*/
// Get the matrixs for rendering
Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks);
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
int farPlaneBlockDistance;
vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
// required for setupFog and setupProjectionMatrix
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
else
farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH;
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, farPlaneBlockDistance);
LodFogConfig fogSettings = new LodFogConfig(CONFIG, REFLECTION_HANDLER, farPlaneBlockDistance, vanillaBlockRenderedDistance);
/*---------Fill uniform data--------*/
// Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0
shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(),
getFogColor(), (int) (MC.getSkyDarken(partialTicks) * 15), 0);
// Previous guy said fog setting may be different from region to region, but the fogSettings never changed... soooooo...
shaderProgram.fillUniformDataForFog(fogSettings);
// Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one.
lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels());
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
boolean usingBufferStorage = glProxy.getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE;
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance);
// where the center of the buffers is (needed when culling regions)
// render each of the buffers
for (int x = 0; x < vbos.length; x++)
LodFogConfig fogSettings = determineFogConfig();
if (vbos != null)
{
for (int z = 0; z < vbos.length; z++)
//==============//
// shader setup //
//==============//
// can be used when testing shaders
// glProxy.createShaderProgram();
LodShaderProgram shaderProgram = glProxy.lodShaderProgram;
shaderProgram.use();
// determine the VertexArrayObject's element positions
int posAttrib = shaderProgram.getAttributeLocation("vPosition");
shaderProgram.enableVertexAttribute(posAttrib);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
int blockSkyLightAttrib = shaderProgram.getAttributeLocation("blockSkyLight");
// TODO the block sky light is being passed in correctly but the data
// we were given appears to be incorrect, so we won't use it for now
//shaderProgram.enableVertexAttribute(blockSkyLightAttrib);
int blockLightAttrib = shaderProgram.getAttributeLocation("blockLight");
shaderProgram.enableVertexAttribute(blockLightAttrib);
// global uniforms
int mvmUniform = shaderProgram.getUniformLocation("modelViewMatrix");
shaderProgram.setUniform(mvmUniform, modelViewMatrix);
int projUniform = shaderProgram.getUniformLocation("projectionMatrix");
shaderProgram.setUniform(projUniform, projectionMatrix);
int cameraUniform = shaderProgram.getUniformLocation("cameraPos");
shaderProgram.setUniform(cameraUniform, getTranslatedCameraPos());
int fogColorUniform = shaderProgram.getUniformLocation("fogColor");
shaderProgram.setUniform(fogColorUniform, getFogColor());
int skyLightUniform = shaderProgram.getUniformLocation("worldSkyLight");
shaderProgram.setUniform(skyLightUniform, (int) (MC.getSkyDarken(partialTicks) * 15));
int lightMapUniform = shaderProgram.getUniformLocation("lightMap");
GL20.glBindTexture(GL20.GL_TEXTURE_2D, glProxy.lightMapTextureId);
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_BORDER);
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_BORDER);
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_NEAREST);
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
// get the latest lightmap from MC
try
{
//int tempX = LodUtil.convertLevelPos(x + vbosCenterX - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL);
//int tempY = LodUtil.convertLevelPos(z + vbosCenterZ - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL, LodUtil.BLOCK_DETAIL_LEVEL);
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
MC_RENDER.getLookAtVector(),
vbosCenterX + LodUtil.convertLevelPos(x - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL),
vbosCenterZ + LodUtil.convertLevelPos(z - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL)))
int lightMapHeight = MC_RENDER.getLightmapTextureHeight();
int lightMapWidth = MC_RENDER.getLightmapTextureWidth();
int[] pixels = MC_RENDER.getLightmapPixels();
// comment me out to see when the lightmap is changing
// boolean same = true;
// int badIndex = 0;
// if (testArray != null && pixels != null)
// for (int i = 0; i < pixels.length; i++)
// {
// if(pixels[i] != testArray[i])
// {
// same = false;
// badIndex = i;
// break;
// }
// }
// testArray = pixels;
// MC.sendChatMessage(same + " " + badIndex);
// comment this line out to prevent uploading the new lightmap
GL20.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_RGBA, lightMapWidth,
lightMapHeight, 0, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels);
// TODO is this needed/correct?
shaderProgram.setUniform(lightMapUniform, 0);
}
catch (Exception e)
{
ClientApi.LOGGER.info(e.getMessage(), e);
}
// region dependent uniforms
int fogEnabledUniform = shaderProgram.getUniformLocation("fogEnabled");
int nearFogEnabledUniform = shaderProgram.getUniformLocation("nearFogEnabled");
int farFogEnabledUniform = shaderProgram.getUniformLocation("farFogEnabled");
// near
int nearFogStartUniform = shaderProgram.getUniformLocation("nearFogStart");
int nearFogEndUniform = shaderProgram.getUniformLocation("nearFogEnd");
// far
int farFogStartUniform = shaderProgram.getUniformLocation("farFogStart");
int farFogEndUniform = shaderProgram.getUniformLocation("farFogEnd");
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
boolean renderBufferStorage = CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE && glProxy.bufferStorageSupported;
// where the center of the buffers is (needed when culling regions)
int vboCenterRegionPosX = vbosCenterX;
int vboCenterRegionPosZ = vbosCenterZ;
int vboPosX;
int vboPosZ;
// render each of the buffers
for (int x = 0; x < vbos.length; x++)
{
for (int z = 0; z < vbos.length; z++)
{
vboPosX = x + vboCenterRegionPosX - (lodDim.getWidth() / 2);
vboPosZ = z + vboCenterRegionPosZ - (lodDim.getWidth() / 2);
// actual rendering
int bufferId = 0;
for (int i = 0; i < vbos[x][z].length; i++)
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
MC_RENDER.getLookAtVector(),
LodUtil.convertLevelPos(LodUtil.REGION_DETAIL_LEVEL, vboPosX, LodUtil.BLOCK_DETAIL_LEVEL),
LodUtil.convertLevelPos(LodUtil.REGION_DETAIL_LEVEL, vboPosZ, LodUtil.BLOCK_DETAIL_LEVEL)))
{
bufferId = (storageBufferIds != null && usingBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
if (bufferId==0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, bufferId);
shaderProgram.bindVertexBuffer(bufferId);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbos[x][z][i].vertexCount);
// fog may be different from region to region
applyFog(shaderProgram,
fogSettings, fogEnabledUniform, nearFogEnabledUniform, farFogEnabledUniform,
nearFogStartUniform, nearFogEndUniform, farFogStartUniform, farFogEndUniform);
// actual rendering
int bufferId = 0;
for (int i = 0; i < vbos[x][z].length; i++)
{
bufferId = (storageBufferIds != null && renderBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
drawArrays(bufferId, vbos[x][z][i].vertexCount, posAttrib, colAttrib, blockLightAttrib, blockSkyLightAttrib);
}
}
}
}
GL20.glBindTexture(GL20.GL_TEXTURE_2D, 0);
//================//
// render cleanup //
//================//
// if this cleanup isn't done MC will crash
// when trying to render its own terrain
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
GL20.glDisableVertexAttribArray(posAttrib);
GL20.glDisableVertexAttribArray(colAttrib);
// GL20.glDisableVertexAttribArray(blockSkyLightAttrib);
GL20.glDisableVertexAttribArray(blockLightAttrib);
}
//================//
// render cleanup //
//================//
//=========//
// cleanup //
//=========//
profiler.popPush("LOD cleanup");
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
shaderProgram.unbind();
lightmapTexture.free();
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
if (currentBlend)
GL32.glEnable(GL32.GL_BLEND);
else
GL32.glDisable(GL32.GL_BLEND);
// if this cleanup isn't done MC will crash
// when trying to render its own terrain
// And may causes mod compat issue
GL32.glUseProgram(currentProgram);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, currentVBO);
GL32.glBindVertexArray(currentVAO);
GL32.glActiveTexture(currentActiveText);
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
GL15.glDisable(GL15.GL_BLEND); // TODO: what should this be reset to?
// clear the depth buffer so everything is drawn over the LODs
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
GL20.glUseProgram(currentProgram);
// clear the depth buffer so everything is drawn
// over the LODs
GL15.glClear(GL15.GL_DEPTH_BUFFER_BIT);
// end of internal LOD profiling
profiler.pop();
}
// Temporary variables James was using while working with the shader lightmap
int[] testArray = null;
int testInt = 0;
/** This is where the actual drawing happens. */
private void drawArrays(int glBufferId, int vertexCount, int posAttrib, int colAttrib, int blockLightAttrib, int blockSkyLightAttrib)
{
if (glBufferId == 0)
return;
// can be used to check for OpenGL errors
// int error = GL15.glGetError();
// ClientApi.LOGGER.info(Integer.toHexString(error));
// bind the buffer we are going to draw
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, glBufferId);
GL30.glBindVertexArray(GLProxy.getInstance().vertexArrayObjectId);
// let OpenGL know how our buffer is set up
int vertexByteCount = (Float.BYTES * 3) + (Byte.BYTES * 4) + Byte.BYTES + Byte.BYTES; // TODO move this into the template
GL20.glEnableVertexAttribArray(posAttrib);
GL20.glVertexAttribPointer(posAttrib, 3, GL15.GL_FLOAT, false, vertexByteCount, 0);
GL20.glEnableVertexAttribArray(colAttrib);
GL20.glVertexAttribPointer(colAttrib, 4, GL15.GL_UNSIGNED_BYTE, true, vertexByteCount, Float.BYTES * 3);
GL20.glEnableVertexAttribArray(blockLightAttrib);
GL20.glVertexAttribPointer(blockLightAttrib, 1, GL15.GL_UNSIGNED_BYTE, false, vertexByteCount, Float.BYTES * (3 + 1));
// GL20.glEnableVertexAttribArray(blockSkyLightAttrib);
// GL20.glVertexAttribPointer(blockSkyLightAttrib, 1, GL15.GL_UNSIGNED_BYTE, false, vertexByteCount, Float.BYTES * (3 + 1 + 1));
// draw the LODs
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, vertexCount);
}
//=================//
// Setup Functions //
//=================//
/** Setup all render objects - REQUIRES to be in render thread */
private void setup() {
if (isSetupComplete) {
ClientApi.LOGGER.warn("Renderer setup called but it has already completed setup!");
return;
}
if (!GLProxy.hasInstance()) {
ClientApi.LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
return;
}
isSetupComplete = true;
shaderProgram = new LodRenderProgram();
}
/** Create all buffers that will be used. */
public void setupBuffers(LodDimension lodDim)
{
lodBufferBuilderFactory.setupBuffers(lodDim);
}
/** Return what fog settings should be used when rendering. */
private LodFogConfig determineFogConfig()
{
LodFogConfig fogConfig = new LodFogConfig();
fogConfig.fogDrawMode = CONFIG.client().graphics().fogQuality().getFogDrawMode();
if (fogConfig.fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
fogConfig.fogDrawMode = REFLECTION_HANDLER.getFogDrawMode();
// how different distances are drawn depends on the quality set
fogConfig.fogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
// far fog //
if (CONFIG.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
fogConfig.farFogStart = farPlaneBlockDistance * 0.9f;
else
// for more realistic fog when using FAR
fogConfig.farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f);
fogConfig.farFogEnd = farPlaneBlockDistance;
// near fog //
// the reason that I wrote fogEnd then fogStart backwards
// is because we are using fog backwards to how
// it is normally used, hiding near objects
// instead of far objects.
fogConfig.nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
fogConfig.nearFogStart = vanillaBlockRenderedDistance * 1.6f;
return fogConfig;
}
private Color getFogColor()
{
Color fogColor;
@@ -397,10 +584,8 @@ public class LodRenderer
// translate the camera relative to the regions' center
// (AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
// accuracy vs the model view matrix, which only uses floats)
//int bufferPosX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
//int bufferPosZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
int bufferPosX = vbosCenterX;
int bufferPosZ = vbosCenterZ;
int bufferPosX = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterX, LodUtil.CHUNK_DETAIL_LEVEL);
int bufferPosZ = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterZ, LodUtil.CHUNK_DETAIL_LEVEL);
double xDiff = projectedView.x - bufferPosX;
double zDiff = projectedView.z - bufferPosZ;
mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff);
@@ -414,10 +599,8 @@ public class LodRenderer
*/
private Vec3f getTranslatedCameraPos()
{
//int worldCenterX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
//int worldCenterZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
int worldCenterX = vbosCenterX;
int worldCenterZ = vbosCenterZ;
int worldCenterX = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterX, LodUtil.CHUNK_DETAIL_LEVEL);
int worldCenterZ = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterZ, LodUtil.CHUNK_DETAIL_LEVEL);
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
return new Vec3f((float)cameraPos.x - worldCenterX, (float)cameraPos.y, (float)cameraPos.z - worldCenterZ);
}
@@ -427,7 +610,7 @@ public class LodRenderer
* @param currentProjectionMatrix this is Minecraft's current projection matrix
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
*/
private static Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance, int farPlaneBlockDistance)
private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance)
{
//Create a copy of the current matrix, so the current matrix isn't modified.
Mat4f lodProj = currentProjectionMatrix.copy();
@@ -440,37 +623,38 @@ public class LodRenderer
return lodProj;
}
//======================//
// Cleanup Functions //
//======================//
/** cleanup and free all render objects. REQUIRES to be in render thread
* (Many objects are Native, outside of JVM, and need manual cleanup) */
private void cleanup() {
if (!isSetupComplete) {
ClientApi.LOGGER.warn("Renderer cleanup called but Renderer has not completed setup!");
return;
}
if (!GLProxy.hasInstance()) {
ClientApi.LOGGER.warn("Renderer Cleanup called but the GLProxy has never been inited!");
return;
}
isSetupComplete = false;
ClientApi.LOGGER.info("Renderer Cleanup Started");
shaderProgram.free();
ClientApi.LOGGER.info("Renderer Cleanup Complete");
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
private void applyFog(LodShaderProgram shaderProgram,
LodFogConfig fogSettings, int fogEnabledUniform, int nearFogEnabledUniform, int farFogEnabledUniform,
int nearFogStartUniform, int nearFogEndUniform, int farFogStartUniform, int farFogEndUniform)
{
lodBufferBuilderFactory.destroyBuffers();
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED)
{
shaderProgram.setUniform(fogEnabledUniform, true);
shaderProgram.setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
shaderProgram.setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
// near
shaderProgram.setUniform(nearFogStartUniform, fogSettings.nearFogStart);
shaderProgram.setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
// far
shaderProgram.setUniform(farFogStartUniform, fogSettings.farFogStart);
shaderProgram.setUniform(farFogEndUniform, fogSettings.farFogEnd);
}
else
{
shaderProgram.setUniform(fogEnabledUniform, false);
}
}
//======================//
// Other Misc Functions //
//======================//
/**
* If this is called then the next time "drawLODs" is called
* the LODs will be regenerated; the same as if the player moved.
@@ -494,10 +678,18 @@ public class LodRenderer
VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers();
vbos = result.vbos;
storageBufferIds = result.storageBufferIds;
vbosCenterX = result.drawableCenterBlockPosX;
vbosCenterZ = result.drawableCenterBlockPosZ;
vbosCenterX = result.drawableCenterChunkPosX;
vbosCenterZ = result.drawableCenterChunkPosZ;
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
{
lodBufferBuilderFactory.destroyBuffers();
}
/** Determines if the LODs should have a fullRegen or partialRegen */
private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks)
{
@@ -546,10 +738,46 @@ public class LodRenderer
prevPlayerPosTime = newTime;
}
/*
// determine how far the lighting has to
// change in order to rebuild the buffers
// the max brightness is 1 and the minimum is 0.2
float skyBrightness = lodDim.dimension.hasSkyLight() ? MC.getSkyDarken(partialTicks) : 0.2f;
float minLightingDifference;
switch (CONFIG.client().advanced().buffers().getRebuildTimes())
{
case FREQUENT:
minLightingDifference = 0.025f;
break;
case NORMAL:
minLightingDifference = 0.05f;
break;
default:
case RARE:
minLightingDifference = 0.1f;
break;
}
// check if the lighting changed
if (Math.abs(skyBrightness - prevSkyBrightness) > minLightingDifference
// make sure the lighting gets to the max/minimum value
// (just in case the minLightingDifference is too large to notice the change)
|| (skyBrightness == 1.0f && prevSkyBrightness != 1.0f) // noon
|| (skyBrightness == 0.2f && prevSkyBrightness != 0.2f) // midnight
|| MC_RENDER.getGamma() != prevBrightness)
{
fullRegen = true;
prevBrightness = MC_RENDER.getGamma();
prevSkyBrightness = skyBrightness;
}*/
//================//
// partial regens //
//================//
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
{
@@ -561,6 +789,7 @@ public class LodRenderer
prevVanillaChunkTime = newTime;
}
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
{
@@ -572,6 +801,8 @@ public class LodRenderer
prevChunkTime = newTime;
}
//==============//
// LOD skipping //
//==============//
@@ -613,5 +844,5 @@ public class LodRenderer
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
}
}
@@ -1,59 +0,0 @@
package com.seibel.lod.core.render.objects;
import org.lwjgl.opengl.GL32;
public class LightmapTexture {
public int id;
public LightmapTexture() {
id = GL32.glGenTextures();
bind();
}
public void bind() {
GL32.glBindTexture(GL32.GL_TEXTURE_2D, id);
}
public void unbind() {
GL32.glBindTexture(GL32.GL_TEXTURE_2D, 0);
}
public void free() {
GL32.glDeleteTextures(id);
}
// private int[] testArray;
public void fillData(int lightMapWidth, int lightMapHeight, int[] pixels) {
GL32.glDeleteTextures(id);
id = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, id);
if (pixels.length != lightMapWidth*lightMapHeight)
throw new RuntimeException("Lightmap Width*Height not equal to pixels provided!");
// comment me out to see when the lightmap is changing
/*
boolean same = true;
int badIndex = 0;
if (testArray != null && pixels != null)
for (int i = 0; i < pixels.length; i++)
{
if(pixels[i] != testArray[i])
{
same = false;
badIndex = i;
break;
}
}
testArray = pixels;
MC.sendChatMessage(same + " " + badIndex);
*/
// comment this line out to prevent uploading the new lightmap
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA, lightMapWidth,
lightMapHeight, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_BYTE, pixels);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_BORDER);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_BORDER);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
}
}
@@ -1,169 +0,0 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.objects;
import java.awt.Color;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryStack;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
/**
* This object holds the reference to a OpenGL shader program
* and contains a few methods that can be used with OpenGL shader programs.
* The reason for many of these simple wrapper methods is as reminders of what
* can (and needs to be) done with a shader program.
*
* @author James Seibel
* @version 11-26-2021
*/
public class ShaderProgram
{
/** Stores the handle of the program. */
public final int id;
// TODO: A better way to set the fragData output name
/** Creates a shader program.
* This will bind ShaderProgram */
public ShaderProgram(String vert, String frag, String fragDataOutputName)
{
Shader vertShader = new Shader(GL32.GL_VERTEX_SHADER, vert, false);
Shader fragShader = new Shader(GL32.GL_FRAGMENT_SHADER, frag, false);
id = GL32.glCreateProgram();
GL32.glAttachShader(this.id, vertShader.id);
GL32.glAttachShader(this.id, fragShader.id);
//GL32.glBindFragDataLocation(id, 0, fragDataOutputName);
GL32.glLinkProgram(this.id);
vertShader.free(); // important!
fragShader.free(); // important!
int status = GL32.glGetProgrami(this.id, GL32.GL_LINK_STATUS);
if (status != GL32.GL_TRUE) {
String message = "Shader Link Error. Details: "+GL32.glGetProgramInfoLog(this.id);
free(); // important!
throw new RuntimeException(message);
}
GL32.glUseProgram(id); // This HAVE to be a direct call to prevent calling the overloaded version
}
/** This will bind ShaderProgram */
public void bind()
{
GL32.glUseProgram(id);
}
/** This will unbind ShaderProgram */
public void unbind() {
GL32.glUseProgram(0);
}
// REMEMBER to always free the resource!
public void free()
{
GL32.glDeleteProgram(id);
}
/** WARNING: Slow native call! Cache it if possible!
* Gets the location of an attribute variable with specified name.
* Calls GL20.glGetAttribLocation(id, name)
*
* @param name Attribute name
* @throws RuntimeException if attribute not found
* @return Location of the attribute
*/
public int getAttributeLocation(CharSequence name)
{
int i = GL32.glGetAttribLocation(id, name);
if (i==-1) throw new RuntimeException("Attribute name not found: "+name);
return i;
}
/** WARNING: Slow native call! Cache it if possible!
* Gets the location of a uniform variable with specified name.
* Calls GL20.glGetUniformLocation(id, name)
*
* @param name Uniform name
* @throws RuntimeException if uniform not found
* @return Location of the Uniform
*/
public int getUniformLocation(CharSequence name)
{
int i = GL32.glGetUniformLocation(id, name);
if (i==-1) throw new RuntimeException("Uniform name not found: "+name);
return i;
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, boolean value)
{
// This use -1 for false as that equals all one set
GL32.glUniform1i(location, value ? 1 : 0);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, int value)
{
GL32.glUniform1i(location, value);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, float value)
{
GL32.glUniform1f(location, value);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Vec3f value)
{
GL32.glUniform3f(location, value.x, value.y, value.z);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Vec3d value)
{
GL32.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Mat4f value)
{
try (MemoryStack stack = MemoryStack.stackPush())
{
FloatBuffer buffer = stack.mallocFloat(4 * 4);
value.store(buffer);
GL32.glUniformMatrix4fv(location, false, buffer);
}
}
/** Converts the color's RGBA values into values between 0 and 1.
* Requires ShaderProgram binded. */
public void setUniform(int location, Color value)
{
GL32.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
}
}
@@ -1,106 +0,0 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.objects;
import org.lwjgl.opengl.GL32;
public abstract class VertexAttribute {
public static final class VertexPointer {
public final int elementCount;
public final int glType;
public final boolean normalized;
public final int byteSize;
public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize) {
this.elementCount = elementCount;
this.glType = glType;
this.normalized = normalized;
this.byteSize = byteSize;
}
public static VertexPointer addFloatPointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_FLOAT, normalized, 4);
}
public static VertexPointer addVec2Pointer(boolean normalized) {
return new VertexPointer(2, GL32.GL_FLOAT, normalized, 8);
}
public static VertexPointer addVec3Pointer(boolean normalized) {
return new VertexPointer(3, GL32.GL_FLOAT, normalized, 12);
}
public static VertexPointer addVec4Pointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_FLOAT, normalized, 16);
}
public static VertexPointer addUnsignedBytePointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 1);
}
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized) {
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, elementCount);
}
public static VertexPointer addIntPointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_INT, normalized, 4);
}
public static VertexPointer addIvec2Pointer(boolean normalized) {
return new VertexPointer(2, GL32.GL_INT, normalized, 8);
}
public static VertexPointer addIvec3Pointer(boolean normalized) {
return new VertexPointer(3, GL32.GL_INT, normalized, 12);
}
public static VertexPointer addIvec4Pointer(boolean normalized) {
return new VertexPointer(4, GL32.GL_INT, normalized, 16);
}
}
/** Stores the handle of the VertexAttribute. */
public final int id;
// This will bind VertexAttribute
protected VertexAttribute() {
id = GL32.glGenVertexArrays();
GL32.glBindVertexArray(id);
}
// This will bind VertexAttribute
public void bind() {
GL32.glBindVertexArray(id);
}
// This will unbind VertexAttribute
public void unbind() {
GL32.glBindVertexArray(0);
}
// REMEMBER to always free the resource!
public void free() {
GL32.glDeleteVertexArrays(id);
}
// Requires VertexAttribute binded, VertexBuffer binded
public abstract void bindBufferToAllBindingPoint(int buffer);
// Requires VertexAttribute binded, VertexBuffer binded
public abstract void bindBufferToBindingPoint(int buffer, int bindingPoint);
// Requires VertexAttribute binded
public abstract void unbindBuffersFromAllBindingPoint();
// Requires VertexAttribute binded
public abstract void unbindBuffersFromBindingPoint(int bindingPoint);
// Requires VertexAttribute binded
public abstract void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute);
// Requires VertexAttribute binded
public abstract void completeAndCheck(int expectedStrideSize);
}
@@ -1,73 +0,0 @@
package com.seibel.lod.core.render.objects;
import org.lwjgl.opengl.GL43;
import com.seibel.lod.core.api.ClientApi;
// In OpenGL 4.3 and later, Vertex Attribute got a make-over.
// Now it provides support for buffer binding points natively.
// This means that setting up the VAO just use ONE native call when
// binding to a buffer.
//
// Since I no longer needs to implement binding points, I also no
// longer needs to keep track of Pointers.
public final class VertexAttributePostGL43 extends VertexAttribute {
int numberOfBindingPoints = 0;
int strideSize = 0;
// This will bind VertexAttribute
public VertexAttributePostGL43() {
super(); // also bind VertexAttribute
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToAllBindingPoint(int buffer) {
for (int i=0; i<numberOfBindingPoints; i++)
GL43.glBindVertexBuffer(i, buffer, 0, strideSize);
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToBindingPoint(int buffer, int bindingPoint) {
GL43.glBindVertexBuffer(bindingPoint, buffer, 0, strideSize);
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromAllBindingPoint() {
for (int i=0; i<numberOfBindingPoints; i++)
GL43.glBindVertexBuffer(i, 0, 0, 0);
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromBindingPoint(int bindingPoint) {
GL43.glBindVertexBuffer(bindingPoint, 0, 0, 0);
}
@Override
// Requires VertexAttribute binded
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
GL43.glVertexAttribFormat(attributeIndex, attribute.elementCount, attribute.glType,
attribute.normalized, strideSize); // Here strideSize is new attrib offset
strideSize += attribute.byteSize;
if (numberOfBindingPoints <= bindingPoint) numberOfBindingPoints = bindingPoint+1;
GL43.glVertexAttribBinding(attributeIndex, bindingPoint);
GL43.glEnableVertexAttribArray(attributeIndex);
}
@Override
// Requires VertexAttribute binded
public void completeAndCheck(int expectedStrideSize) {
if (strideSize != expectedStrideSize) {
ClientApi.LOGGER.error("Vertex Attribute calculated stride size " + strideSize +
" does not match the provided expected stride size " + expectedStrideSize + "!");
}
ClientApi.LOGGER.info("Vertex Attribute (GL43+) completed. It contains "+numberOfBindingPoints
+" binding points and a stride size of "+strideSize);
}
}
@@ -1,149 +0,0 @@
package com.seibel.lod.core.render.objects;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.TreeSet;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ClientApi;
public final class VertexAttributePreGL43 extends VertexAttribute {
// I tried to use as much raw arrays as possible as those lookups
// happens every frame, and the speed directly effects fps
int strideSize = 0;
int[][] bindingPointsToIndex;
VertexPointer[] pointers;
int[] pointersOffset;
TreeMap<Integer, TreeSet<Integer>> bindingPointsToIndexBuilder;
ArrayList<VertexPointer> pointersBuilder;
// This will bind VertexAttribute
public VertexAttributePreGL43() {
super(); // also bind VertexAttribute
bindingPointsToIndexBuilder = new TreeMap<Integer, TreeSet<Integer>>();
pointersBuilder = new ArrayList<VertexPointer>();
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToAllBindingPoint(int buffer) {
for (int i=0; i<pointers.length; i++)
GL32.glEnableVertexAttribArray(i);
for (int i=0; i< pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer==null) continue;
GL32.glVertexAttribPointer(i, pointer.elementCount, pointer.glType,
pointer.normalized, strideSize, pointersOffset[i]);
}
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToBindingPoint(int buffer, int bindingPoint) {
int[] toBind = bindingPointsToIndex[bindingPoint];
for (int i=0; i<toBind.length; i++)
GL32.glEnableVertexAttribArray(toBind[i]);
for (int i=0; i< toBind.length; i++) {
VertexPointer pointer = pointers[toBind[i]];
if (pointer==null) continue;
GL32.glVertexAttribPointer(toBind[i], pointer.elementCount, pointer.glType,
pointer.normalized, strideSize, pointersOffset[toBind[i]]);
}
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromAllBindingPoint() {
for (int i=0; i<pointers.length; i++)
GL32.glDisableVertexAttribArray(i);
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromBindingPoint(int bindingPoint) {
int[] toBind = bindingPointsToIndex[bindingPoint];
for (int i=0; i<toBind.length; i++)
GL32.glDisableVertexAttribArray(toBind[i]);
}
@Override
// Requires VertexAttribute binded
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
TreeSet<Integer> intArray = bindingPointsToIndexBuilder.get(bindingPoint);
if (intArray == null) {
intArray = new TreeSet<Integer>();
bindingPointsToIndexBuilder.put(bindingPoint, intArray);
}
intArray.add(attributeIndex);
while (pointersBuilder.size() <= attributeIndex) {
// This is dumb, but ArrayList doesn't have a resize, And this code
// should only be ran when it's building the Vertex Attribute anyways.
pointersBuilder.add(null);
}
pointersBuilder.set(attributeIndex, attribute);
}
@Override
// Requires VertexAttribute binded
public void completeAndCheck(int expectedStrideSize) {
int maxBindPointNumber = bindingPointsToIndexBuilder.lastKey();
bindingPointsToIndex = new int[maxBindPointNumber+1][];
bindingPointsToIndexBuilder.forEach((Integer i, TreeSet<Integer> set) -> {
bindingPointsToIndex[i] = new int[set.size()];
Iterator<Integer> iter = set.iterator();
for (int j = 0; j<set.size(); j++) {
bindingPointsToIndex[i][j] = iter.next();
}
});
pointers = pointersBuilder.toArray(new VertexPointer[pointersBuilder.size()]);
pointersOffset = new int[pointers.length];
pointersBuilder = null; // Release the builder
bindingPointsToIndexBuilder = null; // Release the builder
// Check if all pointers are valid
int currentOffset = 0;
for (int i = 0; i < pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer == null) {
ClientApi.LOGGER.warn("Vertex Attribute index "+i+" is not set! No index should be skipped normally!");
continue;
}
pointersOffset[i] = currentOffset;
currentOffset += pointer.byteSize;
}
if (currentOffset != expectedStrideSize)
ClientApi.LOGGER.error("Vertex Attribute calculated stride size " + currentOffset +
" does not match the provided expected stride size " + expectedStrideSize + "!");
strideSize = currentOffset;
ClientApi.LOGGER.info("Vertex Attribute (pre GL43) completed.");
// Debug logging
ClientApi.LOGGER.info("Vertex Attribute Debug Data:");
ClientApi.LOGGER.info("AttributeIndex: ElementCount, glType, normalized, strideSize, offset");
for (int i=0; i< pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer==null) {
ClientApi.LOGGER.warn(i + ": Null!!!!");
continue;
}
ClientApi.LOGGER.info(i + ": "+pointer.elementCount+", "+
pointer.glType+", "+pointer.normalized+", "+strideSize+", "+pointersOffset[i]);
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.objects;
package com.seibel.lod.core.render.shader;
import java.io.BufferedReader;
import java.io.FileInputStream;
@@ -26,9 +26,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ClientApi;
import org.lwjgl.opengl.GL20;
/**
* This object holds a OpenGL reference to a shader
@@ -37,42 +35,31 @@ import com.seibel.lod.core.api.ClientApi;
* @author James Seibel
* @version 11-8-2021
*/
public class Shader
public class LodShader
{
/** OpenGL shader ID */
public final int id;
/** Creates a shader with specified type.
/** Creates a shader with specified type. */
public LodShader(int type)
{
id = GL20.glCreateShader(type);
}
/**
* Loads a shader from file.
*
* @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
* @param path File path of the shader
* @param absoluteFilePath If false the file path is relative to the resource jar folder.
* @throws RuntimeException if the shader fails to compile
* @throws Exception if the shader fails to compile
*/
public Shader(int type, String path, boolean absoluteFilePath)
public static LodShader loadShader(int type, String path, boolean absoluteFilePath)
{
ClientApi.LOGGER.info("Loading shader at "+path);
// Create an empty shader object
id = GL32.glCreateShader(type);
StringBuilder source = loadFile(path, absoluteFilePath);
GL32.glShaderSource(id, source);
GL32.glCompileShader(id);
// check if the shader compiled
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS);
if (status != GL32.GL_TRUE) {
String message = "Shader compiler error. Details: "+GL32.glGetShaderInfoLog(id);
free(); // important!
throw new RuntimeException(message);
}
ClientApi.LOGGER.info("Shader at "+path+" loaded sucessfully.");
}
// REMEMBER to always free the resource!
public void free() {
GL32.glDeleteShader(id);
}
private StringBuilder loadFile(String path, boolean absoluteFilePath) {
StringBuilder stringBuilder = new StringBuilder();
try
@@ -83,7 +70,7 @@ public class Shader
// Throws FileNotFoundException
in = new FileInputStream(path); // Note: this should use OS path seperator
} else {
in = Shader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
in = LodShader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
if (in == null) {
throw new FileNotFoundException("Shader file not found in resource: "+path);
}
@@ -99,6 +86,39 @@ public class Shader
{
throw new RuntimeException("Unable to load shader from file [" + path + "]. Error: " + e.getMessage());
}
return stringBuilder;
CharSequence shaderFileSource = stringBuilder.toString();
return createShader(type, shaderFileSource);
}
/**
* Creates a shader with the specified type and source.
*
* @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
* @param source Source of the shader
* @throws Exception if the shader fails to compile
*/
public static LodShader createShader(int type, CharSequence source)
{
LodShader shader = new LodShader(type);
GL20.glShaderSource(shader.id, source);
shader.compile();
return shader;
}
/**
* Compiles the shader and checks its status afterwards.
* @throws Exception if the shader fails to compile
*/
public void compile()
{
GL20.glCompileShader(id);
// check if the shader compiled
int status = GL20.glGetShaderi(id, GL20.GL_COMPILE_STATUS);
if (status != GL20.GL_TRUE)
throw new RuntimeException("Shader compiler error. Details: "+GL20.glGetShaderInfoLog(id));
}
}
@@ -0,0 +1,203 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.shader;
import java.awt.Color;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL20;
import org.lwjgl.system.MemoryStack;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
/**
* This object holds the reference to a OpenGL shader program
* and contains a few methods that can be used with OpenGL shader programs.
* The reason for many of these simple wrapper methods is as reminders of what
* can (and needs to be) done with a shader program.
*
* @author James Seibel
* @version 11-26-2021
*/
public class LodShaderProgram
{
/** Stores the handle of the program. */
public final int id;
/** Creates a shader program. */
public LodShaderProgram()
{
id = GL20.glCreateProgram();
}
/** Calls GL20.glUseProgram(this.id) */
public void use()
{
GL20.glUseProgram(id);
}
/**
* Calls GL20.glAttachShader(this.id, shader.id)
*
* @param shader Shader to get attached
*/
public void attachShader(LodShader shader)
{
GL20.glAttachShader(this.id, shader.id);
}
/**
* Links the shader program to the current OpenGL context.
* @throws Exception Exception if the program failed to link
*/
public void link()
{
GL20.glLinkProgram(this.id);
checkLinkStatus();
}
/**
* Checks if the program was linked successfully.
* @throws Exception if the program failed to link
*/
public void checkLinkStatus()
{
int status = GL20.glGetProgrami(this.id, GL20.GL_LINK_STATUS);
if (status != GL20.GL_TRUE)
throw new RuntimeException("Shader Link Error. Details: "+GL20.glGetProgramInfoLog(this.id));
}
/**
* Gets the location of an attribute variable with specified name.
* Calls GL20.glGetAttribLocation(id, name)
*
* @param name Attribute name
*
* @return Location of the attribute
*/
public int getAttributeLocation(CharSequence name)
{
return GL20.glGetAttribLocation(id, name);
}
/**
* Calls GL20.glEnableVertexAttribArray(location)
*
* @param location Location of the vertex attribute
*/
public void enableVertexAttribute(int location)
{
GL20.glEnableVertexAttribArray(location);
}
/**
* Calls GL20.glDisableVertexAttribArray(location)
*
* @param location Location of the vertex attribute
*/
public void disableVertexAttribute(int location)
{
GL20.glDisableVertexAttribArray(location);
}
/**
* Sets the vertex attribute pointer.
* Calls GL20.glVertexAttribPointer(...)
*
* @param location Location of the vertex attribute
* @param size Number of values per vertex
* @param stride Offset between consecutive generic vertex attributes in
* bytes
* @param offset Offset of the first component of the first generic vertex
* attribute in bytes
*/
public void pointVertexAttribute(int location, int size, int stride, int offset)
{
GL20.glVertexAttribPointer(location, size, GL20.GL_FLOAT, false, stride, offset);
}
/**
* Gets the location of a uniform variable with specified name.
* Calls GL20.glGetUniformLocation(id, name)
*
* @param name Uniform name
*
* @return -1 = error value, 0 = first value, 1 = second value, etc.
*/
public int getUniformLocation(CharSequence name)
{
return GL20.glGetUniformLocation(id, name);
}
public void setUniform(int location, boolean value)
{
GL20.glUniform1i(location, value ? 1 : 0);
}
public void setUniform(int location, int value)
{
GL20.glUniform1i(location, value);
}
public void setUniform(int location, float value)
{
GL20.glUniform1f(location, value);
}
public void setUniform(int location, Vec3f value)
{
GL20.glUniform3f(location, value.x, value.y, value.z);
}
public void setUniform(int location, Vec3d value)
{
GL20.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
}
public void setUniform(int location, Mat4f value)
{
try (MemoryStack stack = MemoryStack.stackPush())
{
FloatBuffer buffer = stack.mallocFloat(4 * 4);
value.store(buffer);
GL20.glUniformMatrix4fv(location, false, buffer);
}
}
/** Converts the color's RGBA values into values between 0 and 1. */
public void setUniform(int location, Color value)
{
GL20.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
}
}
@@ -23,7 +23,6 @@ import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactor
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
/**
*
* @author Leonardo Amato
@@ -103,9 +102,6 @@ public class DataPointUtil
public final static long VOID_MASK = 1;
public final static long EXISTENCE_MASK = 1;
public final static long HEIGHT_SHIFTED_MASK = HEIGHT_MASK << HEIGHT_SHIFT;
public final static long DEPTH_SHIFTED_MASK = DEPTH_MASK << DEPTH_SHIFT;
public static long createVoidDataPoint(int generationMode)
{
@@ -144,12 +140,6 @@ public class DataPointUtil
return dataPoint;
}
public static long shiftHeightAndDepth(long dataPoint, short offset) {
long height = (dataPoint + (offset << HEIGHT_SHIFT)) & HEIGHT_SHIFTED_MASK;
long depth = (dataPoint + (offset << DEPTH_SHIFT)) & DEPTH_SHIFTED_MASK;
return dataPoint & ~(HEIGHT_SHIFTED_MASK | DEPTH_SHIFTED_MASK) | height | depth;
}
public static short getHeight(long dataPoint)
{
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
@@ -221,8 +211,7 @@ public class DataPointUtil
public static int getColor(long dataPoint)
{
// TODO re-add transparency by replacing the color 255 with what is in comment
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111) << 24);
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
}
/** This is used to convert a dataPoint to string (useful for the print function) */
@@ -274,18 +263,19 @@ public class DataPointUtil
* @param maxVerticalData max vertical size of the merged data
* @return one column of correctly parsed data
*/
@Deprecated
public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
{
int size = dataToMerge.length / inputVerticalData;
// We initialize the arrays that are going to be used
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT + 1) * 2);
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2);
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
int genMode = DistanceGenerationMode.FULL.complexity;
boolean allEmpty = true;
boolean allVoid = true;
boolean limited = false;
boolean allDefault;
long singleData;
@@ -299,139 +289,115 @@ public class DataPointUtil
//We collect the indexes of the data, ordered by the depth
for (int index = 0; index < size; index++)
{
if (index == 0)
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
singleData = dataToMerge[dataIndex];
if (doesItExist(singleData))
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
int botPos = -1;
int topPos = -1;
//values fall in between and possibly require extension of array
boolean botExtend = false;
boolean topExtend = false;
for (i = 0; i < count; i++)
{
allVoid = false;
count++;
heightAndDepth[dataIndex * 2] = getHeight(singleData);
heightAndDepth[dataIndex * 2 +1] = getDepth(singleData);
if (depth <= heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1])
{
botPos = i;
break;
}
else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
botPos = i;
botExtend = true;
break;
}
}
}
else
break;
}
}
else
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
for (i = 0; i < count; i++)
{
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
int botPos = -1;
int topPos = -1;
//values fall in between and possibly require extension of array
boolean botExtend = false;
boolean topExtend = false;
for (i = 0; i < count; i++)
if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
{
if (depth < heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1])
{
botPos = i;
break;
}
else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth >= heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
botPos = i;
botExtend = true;
break;
}
topPos = i;
break;
}
for (i = 0; i < count; i++)
else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
if (height <= heightAndDepth[i * 2] && height > heightAndDepth[i * 2 + 1])
{
topPos = i;
break;
}
else if (height <= heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
topPos = i;
topExtend = true;
break;
}
topPos = i;
topExtend = true;
break;
}
if (topPos == -1)
}
if (topPos == -1)
{
if (botPos == -1)
{
if (botPos == -1)
{
//whole block falls above
extendArray(heightAndDepth, 2, 0, 1, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count++;
}
else if (!botExtend)
{
//only top falls above extending it there, while bottom is inside existing
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
count -= botPos;
}
else
{
//top falls between some blocks, extending those as well
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count -= botPos;
}
//whole block falls above
extendArray(heightAndDepth, 2, 0, 1, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count++;
}
else if (!topExtend)
else if (!botExtend)
{
if (!botExtend)
//both top and bottom are within some exiting blocks, possibly merging them
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
else
//top falls between some blocks, extending it there
heightAndDepth[topPos * 2 + 1] = depth;
//only top falls above extending it there, while bottom is inside existing
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
count -= botPos;
}
else
{
//top falls between some blocks, extending those as well
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count -= botPos;
}
}
else if (!topExtend)
{
if (!botExtend)
//both top and bottom are within some exiting blocks, possibly merging them
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
else
//top falls between some blocks, extending it there
heightAndDepth[topPos * 2 + 1] = depth;
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
if (!botExtend)
{
//only top is within some exiting block, extending it
topPos++; //to make it easier
heightAndDepth[topPos * 2] = height;
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
if (!botExtend)
{
//only top is within some exiting block, extending it
topPos++; //to make it easier
heightAndDepth[topPos * 2] = height;
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
//both top and bottom are outside existing blocks
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
extendArray(heightAndDepth, 2, topPos + 1, 1, count);
count++;
heightAndDepth[topPos * 2 + 2] = height;
heightAndDepth[topPos * 2 + 3] = depth;
}
//both top and bottom are outside existing blocks
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
extendArray(heightAndDepth, 2, topPos + 1, 1, count);
count++;
heightAndDepth[topPos * 2 + 2] = height;
heightAndDepth[topPos * 2 + 3] = depth;
}
}
}
else
break;
}
else
break;
}
}
@@ -448,8 +414,7 @@ public class DataPointUtil
int j = 0;
while (count > maxVerticalData)
{
limited = true;
ii = WORLD_HEIGHT;
ii = WORLD_HEIGHT - VERTICAL_OFFSET;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
@@ -468,104 +433,88 @@ public class DataPointUtil
count--;
}
//As standard the vertical lods are ordered from top to bottom
if (!limited && size == 1)
for (j = count - 1; j >= 0; j--)
{
for (j = 0; j < count; j++)
dataPoint[j] = dataToMerge[j];
}
else
{
for (j = 0; j < count; j++)
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
{
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
{
if ((depth <= getDepth(singleData) && getDepth(singleData) < height)
|| (depth < getHeight(singleData) && getHeight(singleData) <= height))
{
if (getHeight(singleData) > getHeight(data))
data = singleData;
break;
}
}
else
break;
}
if (!doesItExist(data))
{
singleData = dataToMerge[index * inputVerticalData];
data = createVoidDataPoint(getGenerationMode(singleData));
}
if (doesItExist(data))
{
allEmpty = false;
if (!isVoid(data))
{
numberOfChildren++;
allVoid = false;
tempAlpha += getAlpha(data);
tempRed += getRed(data);
tempGreen += getGreen(data);
tempBlue += getBlue(data);
tempLightBlock += getLightBlock(data);
tempLightSky += getLightSky(data);
if (!getFlag(data))
allDefault = false;
}
tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data));
}
else
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
break;
}
if (!doesItExist(data))
{
singleData = dataToMerge[index * inputVerticalData];
data = createVoidDataPoint(getGenerationMode(singleData));
}
if (allEmpty)
//no child has been initialized
dataPoint[j] = EMPTY_DATA;
else if (allVoid)
//all the children are void
dataPoint[j] = createVoidDataPoint(tempGenMode);
else
if (doesItExist(data))
{
//we have at least 1 child
if (size != 1)
allEmpty = false;
if (!isVoid(data))
{
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
numberOfChildren++;
allVoid = false;
tempAlpha += getAlpha(data);
tempRed += getRed(data);
tempGreen += getGreen(data);
tempBlue += getBlue(data);
tempLightBlock += getLightBlock(data);
tempLightSky += getLightSky(data);
if (!getFlag(data)) allDefault = false;
}
//data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
//if (j > 0 && getColor(data) == getColor(dataPoint[j]))
//{
// add simplification at the end due to color
//}
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data));
}
else
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
if (allEmpty)
//no child has been initialized
dataPoint[j] = EMPTY_DATA;
else if (allVoid)
//all the children are void
dataPoint[j] = createVoidDataPoint(tempGenMode);
else
{
//we have at least 1 child
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
}
}
return dataPoint;
@@ -19,6 +19,7 @@
package com.seibel.lod.core.util;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.HorizontalQuality;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
@@ -133,14 +134,10 @@ public class DetailDistanceUtil
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true);
}
// NOTE: The recent LodWorldGenerator changes assumes that this value doesn't change with 'detail'.
// If this is changed, LodWorldGenerator needs to be fixed!
/*
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
{
return CONFIG.client().worldGenerator().getDistanceGenerationMode();
}*/
}
public static byte getLodDrawDetail(byte detail)
{
@@ -26,7 +26,6 @@ import java.util.HashSet;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
@@ -45,7 +44,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 12-14-2021
* @version 12-8-2021
*/
public class LodUtil
{
@@ -53,7 +52,6 @@ public class LodUtil
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
/**
* Vanilla render distances less than or equal to this will not allow partial
@@ -344,17 +342,18 @@ public class LodUtil
return new HashSet<>();
case DYNAMIC:
if (chunkRenderDist > MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW
&& chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW)
{
// This is a small render distance (but greater than the minimum partial distance)
// skip positions that are greater than 2/3 the render distance
// This is a small render distance (but greater than the minimum partial
// distance), skip positions that are greater than 2/3 the render distance
skipRadius = (int) Math.ceil(chunkRenderDist * (2.0/3.0));
}
else
{
// This is a large render distance.
// Skip positions that are greater than 4/5ths the render distance
// This is a large render distance. Skip positions that are greater than
// 4/5ths the render distance
skipRadius = (int) Math.ceil(chunkRenderDist * (4.0 / 5.0));
}
break;
@@ -370,7 +369,7 @@ public class LodUtil
// get the chunks that are going to be rendered by Minecraft
HashSet<AbstractChunkPosWrapper> posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks();
HashSet<AbstractChunkPosWrapper> posToSkip = MC_RENDER.getRenderedChunks();
// remove everything outside the skipRadius,
@@ -383,9 +382,7 @@ public class LodUtil
{
if (x <= centerChunk.getX() - skipRadius || x >= centerChunk.getX() + skipRadius
|| z <= centerChunk.getZ() - skipRadius || z >= centerChunk.getZ() + skipRadius)
{
posToSkip.remove(FACTORY.createChunkPos(x, z));
}
}
}
}
@@ -28,8 +28,6 @@ import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.objects.VertexOptimizer;
// FIXME: Nuke this whole thing and use ThreadLocal instead. And no more redundant get() please!
/**
* Holds data used by specific threads so
* the data doesn't have to be recreated every
@@ -44,6 +42,7 @@ public class ThreadMapUtil
public static final ConcurrentMap<String, long[][]> threadBuilderArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderVerticalArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> threadVerticalAddDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, byte[][]> saveContainer = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> projectionArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> heightAndDepthMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> singleDataToMergeMap = new ConcurrentHashMap<>();
@@ -118,8 +117,7 @@ public class ThreadMapUtil
//________________________//
//TODO: Maybe use actual valid total world height instead of always assuming the worse and alloc 1024 blocks.
/** returns the array filled with 0's */
public static long[] getBuilderVerticalArray(int detailLevel)
{
@@ -138,22 +136,41 @@ public class ThreadMapUtil
return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array NOT cleared every time */
public static byte[] getSaveContainer(int detailLevel)
{
if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
{
byte[][] array = new byte[LodUtil.DETAIL_OPTIONS][];
int size = 1;
for (int i = LodUtil.DETAIL_OPTIONS - 1; i >= 0; i--)
{
array[i] = new byte[2 + 8 * size * size * DetailDistanceUtil.getMaxVerticalData(i)];
size = size << 1;
}
saveContainer.put(Thread.currentThread().getName(), array);
}
//Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
return saveContainer.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getVerticalDataArray(int arrayLength)
{
long[] array = threadVerticalAddDataMap.get(Thread.currentThread().getName());
if (array == null || array.length != arrayLength)
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
{
array = new long[arrayLength];
threadVerticalAddDataMap.put(Thread.currentThread().getName(), array);
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
}
else
Arrays.fill(array, 0);
return array;
{
Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
}
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
}
//FIXME: If the arrayLength change, this may return incorrect sized array
/** returns the array NOT cleared every time */
public static short[] getHeightAndDepth(int arrayLength)
{
@@ -168,19 +185,18 @@ public class ThreadMapUtil
/** returns the array filled with 0's */
public static long[] getVerticalUpdateArray(int detailLevel)
{
long[][] arrays = verticalUpdate.get(Thread.currentThread().getName());
if (arrays == null)
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
{
arrays = new long[LodUtil.DETAIL_OPTIONS][];
verticalUpdate.put(Thread.currentThread().getName(), arrays);
long[][] array = new long[LodUtil.DETAIL_OPTIONS][];
for (int i = 1; i < LodUtil.DETAIL_OPTIONS; i++)
array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
verticalUpdate.put(Thread.currentThread().getName(), array);
}
long[] array = arrays[detailLevel];
int arrayLength = DetailDistanceUtil.getMaxVerticalData(detailLevel) * 4;
if (array == null || array.length != arrayLength)
array = new long[arrayLength];
else
Arrays.fill(array, 0);
return array;
{
Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0);
}
return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
}
/** clears all arrays so they will have to be rebuilt */
@@ -193,6 +209,7 @@ public class ThreadMapUtil
threadBuilderArrayMap.clear();
threadBuilderVerticalArrayMap.clear();
threadVerticalAddDataMap.clear();
saveContainer.clear();
projectionArrayMap.clear();
heightAndDepthMap.clear();
singleDataToMergeMap.clear();
@@ -1,32 +0,0 @@
package com.seibel.lod.core.wrapperInterfaces;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
/**
* A singleton that contains variables specific to each version of Minecraft
* which can be used to change how DH-Core runs.
* For example: After MC 1.17 blocks can be negative, which changes how we generate LODs.
*
* @author James Seibel
* @version 12-11-2021
*/
public interface IVersionConstants
{
/** @returns the minimum height blocks can be generated */
int getMinimumWorldHeight();
/**
* @Returns True if the given DistanceGenerationMode can be run on our own thread. <br>
* False if the generation must be run on Minecraft's server thread.
*/
boolean isWorldGeneratorSingleThreaded(DistanceGenerationMode distanceGenerationMode);
/**
* @Returns the number of generations call per thread.
*/
default int getWorldGenerationCountPerThread() {
return 8;
}
}
@@ -24,14 +24,13 @@ import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* This handles creating abstract wrapper objects.
*
* @author James Seibel
* @version 12-14-2021
* @version 11-18-2021
*/
public interface IWrapperFactory
{
@@ -40,21 +39,10 @@ public interface IWrapperFactory
AbstractChunkPosWrapper createChunkPos();
public default AbstractChunkPosWrapper createChunkPos(long xAndZPositionCombined)
{
int x = (int) (xAndZPositionCombined & Integer.MAX_VALUE);
int z = (int) (xAndZPositionCombined >> Long.SIZE / 2) & Integer.MAX_VALUE;
return createChunkPos(x, z);
}
AbstractChunkPosWrapper createChunkPos(int x, int z);
AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
// Return null to signal that there is no AbstractWorldGenerator
public default AbstractExperimentalWorldGeneratorWrapper createExperimentalWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) {
return null;
}
}
@@ -23,6 +23,8 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.forge.wrappers.block.BlockShapeWrapper;
import net.minecraft.block.Block;
/**
* @author James Seibel
@@ -26,6 +26,8 @@ import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.config.HorizontalQuality;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.enums.config.HorizontalScale;
import com.seibel.lod.core.enums.config.LodTemplate;
import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.enums.rendering.DebugMode;
@@ -34,7 +36,6 @@ import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.MinDefaultMax;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
/**
* This holds the config defaults, setters/getters
@@ -42,7 +43,7 @@ import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
* the options that should be implemented in a configWrapperSingleton.
*
* @author James Seibel
* @version 12-14-2021
* @version 12-1-2021
*/
public interface ILodConfigWrapperSingleton
{
@@ -121,12 +122,12 @@ public interface ILodConfigWrapperSingleton
HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM;
String HORIZONTAL_QUALITY_DESC = ""
+ " This indicates how much farther away each drop in quality is. \n"
+ " This indicates the exponential base of the quadratic drop-off \n"
+ "\n"
+ " " + HorizontalQuality.LOWEST + ": each drop in quality is the same distance away. \n"
+ " " + HorizontalQuality.LOW + ": each drop in quality is " + HorizontalQuality.LOW.quadraticBase + " times farther away. \n"
+ " " + HorizontalQuality.MEDIUM + ": each drop in quality is " + HorizontalQuality.MEDIUM.quadraticBase + " times farther away. \n"
+ " " + HorizontalQuality.HIGH + ": each drop in quality is " + HorizontalQuality.HIGH.quadraticBase + " times farther away. \n"
+ " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n"
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n"
+ "\n"
+ " Lowest Quality: " + HorizontalQuality.LOWEST
+ " Highest Quality: " + HorizontalQuality.HIGH;
@@ -183,6 +184,7 @@ public interface ILodConfigWrapperSingleton
{
String DESC = "Graphics options that are a bit more technical.";
boolean DISABLE_DIRECTIONAL_CULLING_DEFAULT = false;
String DISABLE_DIRECTIONAL_CULLING_DESC = ""
+ " If false fake chunks behind the player's camera \n"
@@ -213,12 +215,12 @@ public interface ILodConfigWrapperSingleton
+ " HALF and ALWAYS will prevent holes in the world, but may look odd for transparent blocks or in caves. \n"
+ "\n"
+ " " + VanillaOverdraw.NEVER + ": LODs won't render on top of vanilla chunks. \n"
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks, preventing some holes in the world. \n"
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n"
+ " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n"
+ " " + " More effective on higher render distances. \n"
+ " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n"
+ " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " will be used depending on the dimension. \n"
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing all holes in the world. \n"
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n"
+ "\n"
+ " This setting shouldn't affect performance. \n";
VanillaOverdraw getVanillaOverdraw();
@@ -270,16 +272,9 @@ public interface ILodConfigWrapperSingleton
void setGenerationPriority(GenerationPriority newGenerationPriority);
DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
public static String getDistanceGenerationModeDesc(IVersionConstants versionConstants)
{
return ""
String DISTANCE_GENERATION_MODE_DESC = ""
+ " How detailed should fake chunks be generated outside the vanilla render distance? \n"
+ "\n"
+ " The times are the amount of time it took one of the developer's PC to generate \n"
+ " one chunk in Minecraft 1.16.5 and may be inaccurate for different Minecraft versions. \n"
+ " They are included to give a rough estimate as to how the different options \n"
+ " may perform in comparison to each other. \n"
+ "\n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \n"
+ " No CPU usage - Fastest \n"
@@ -288,36 +283,35 @@ public interface ILodConfigWrapperSingleton
+ " Only generate the biomes and use the biome's \n"
+ " grass color, water color, or snow color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY) + " - Fastest (2-5 ms) \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as " + DistanceGenerationMode.BIOME_ONLY + ", except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT) + " - Fastest (2-5 ms) \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include trees, \n"
+ " or structures. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.SURFACE) + " - Faster (10-20 ms) \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FEATURES) + " - Fast (15-20 ms) \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FULL + " \n"
+ " Ask the local server to generate/load each chunk. \n"
+ " This will show player made structures, which can \n"
+ " be useful if you are adding the mod to a pre-existing world. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FULL) + " - Slow (15-50 ms, with spikes up to 200 ms) \n"
+ " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n"
+ "\n"
+ " The multithreaded options may increase CPU load significantly (while generating) \n"
+ " depending on how many world generation threads you have allocated. \n";
}
DistanceGenerationMode getDistanceGenerationMode();
void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode);
@@ -356,13 +350,6 @@ public interface ILodConfigWrapperSingleton
+ " This wont't affect performance.";
BlocksToAvoid getBlocksToAvoid();
void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
/** description helper method */
static String multiOrSingleThreadText(IVersionConstants versionConstants, DistanceGenerationMode distanceGenerationMode)
{
return versionConstants.isWorldGeneratorSingleThreaded(distanceGenerationMode) ? "Singlethreaded" : "Multithreaded";
};
}
@@ -475,7 +462,7 @@ public interface ILodConfigWrapperSingleton
+ " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " Fast rending if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
@@ -486,19 +473,20 @@ public interface ILodConfigWrapperSingleton
GpuUploadMethod getGpuUploadMethod();
void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod);
MinDefaultMax<Integer> GPU_UPLOAD_PER_MEGABYTE_IN_MILLISECONDS_DEFAULT = new MinDefaultMax<Integer>(0, 10, 5000);
String GPU_UPLOAD_PER_MEGABYTE_IN_MILLISECONDS_DESC = ""
+ " How long should a buffer wait per Megabyte of data uploaded?\n"
MinDefaultMax<Integer> GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DEFAULT = new MinDefaultMax<Integer>(0, 0, 5000);
String GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DESC = ""
+ " How long should we wait before uploading a buffer to the GPU? \n"
+ " Helpful resource for frame times: https://fpstoms.com \n"
+ "\n"
+ " Longer times may reduce stuttering but will make fake chunks \n"
+ " transition and load slower. Change this to [0] for no timeout.\n"
+ " transition and load slower. \n"
+ "\n"
+ " NOTE:\n"
+ " Before changing this config, try changing \"GPU Upload methods\"\n"
+ " and determined the best method for your hardware first. \n";
int getGpuUploadPerMegabyteInMilliseconds();
void setGpuUploadPerMegabyteInMilliseconds(int newMilliseconds);
+ " This should be a last resort option."
+ " Only change this from [0], after you have tried all of the \n"
+ " \"GPU Upload methods\" and determined even the best stutters with yoru hardware.";
int getGpuUploadTimeoutInMilliseconds();
void setGpuUploadTimeoutInMilliseconds(int newTimeoutInMilliseconds);
String REBUILD_TIMES_DESC = ""
+ " How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
@@ -25,8 +25,6 @@ import java.util.HashSet;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
@@ -35,7 +33,7 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
* rendering in Minecraft.
*
* @author James Seibel
* @version 12-14-2021
* @version 12-8-2021
*/
public interface IMinecraftRenderWrapper
{
@@ -66,56 +64,9 @@ public interface IMinecraftRenderWrapper
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
public default HashSet<AbstractChunkPosWrapper> getVanillaRenderedChunks()
{
return getMaximumRenderedChunks();
}
/**
* This method returns the ChunkPos of every chunk that
* Sodium is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
public default HashSet<AbstractChunkPosWrapper> getSodiumRenderedChunks()
{
return getMaximumRenderedChunks();
}
/**
* <strong>Doesn't need to be implemented.</strong> <br>
* Returns every chunk position within the vanilla render distance.
*/
public default HashSet<AbstractChunkPosWrapper> getMaximumRenderedChunks()
{
IMinecraftWrapper mcWrapper = SingletonHandler.get(IMinecraftWrapper.class);
IWrapperFactory factory = SingletonHandler.get(IWrapperFactory.class);
int chunkRenderDist = this.getRenderDistance();
// if we have a odd render distance, we'll have a empty gap. This way we'll overlap by 1 instead,
// which is preferable to having a hole in the world
chunkRenderDist = chunkRenderDist % 2 == 0 ? chunkRenderDist : chunkRenderDist - 1;
AbstractChunkPosWrapper centerChunkPos = mcWrapper.getPlayerChunkPos();
int startChunkX = centerChunkPos.getX() - chunkRenderDist;
int startChunkZ = centerChunkPos.getZ() - chunkRenderDist;
// add every position within render distance
HashSet<AbstractChunkPosWrapper> renderedPos = new HashSet<AbstractChunkPosWrapper>();
for (int chunkX = 0; chunkX < (chunkRenderDist * 2); chunkX++)
{
for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2); chunkZ++)
{
renderedPos.add(factory.createChunkPos(startChunkX + chunkX, startChunkZ + chunkZ));
}
}
return renderedPos;
}
HashSet<AbstractChunkPosWrapper> getRenderedChunks();
/** @returns null if there was a issue getting the lightmap */
int[] getLightmapPixels();
@@ -125,10 +76,4 @@ public interface IMinecraftRenderWrapper
int getLightmapTextureWidth();
/** @returns -1 if there was an issue getting the lightmap */
public int getLightmapGLFormat();
/** Try and disable vanilla fog. Return true if successful, or false if not able to.
* If we are still using legacy fog, this method will not be called. */
public default boolean tryDisableVanillaFog() {
return false;
}
}
@@ -52,11 +52,6 @@ public interface IWorldWrapper
int getSeaLevel();
default short getMinHeight()
{
return 0;
}
/** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
File getSaveFolder() throws UnsupportedOperationException;
@@ -1,11 +0,0 @@
package com.seibel.lod.core.wrapperInterfaces.worldGeneration;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public abstract class AbstractExperimentalWorldGeneratorWrapper {
public AbstractExperimentalWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { }
public abstract void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder);
public abstract void stop();
}
+2 -2
View File
@@ -34,8 +34,8 @@ void main()
float skyLightTex = blockSkyLight / 16.0;
// we don't really need alpha in the lightmap
// vertexColor = color * vec4(texture(lightMap, vec2(skyLightTex, blockLightTex)).xyz, 1);
vertexColor = color * texture(lightMap, vec2(skyLightTex, blockLightTex));
// vertexColor = color * vec4(texture(lightMap, vec2(blockLightTex, skyLightTex)).xyz, 1);
vertexColor = color * texture(lightMap, vec2(blockLightTex, skyLightTex));