Overhaul Rendering Buffer Management

+ Merging with Leo's fix to adjData and stuff
This commit is contained in:
tom lee
2022-02-24 21:09:36 +08:00
parent 7ad2e82646
commit 1dcc973a24
12 changed files with 734 additions and 318 deletions
@@ -1,43 +0,0 @@
package com.seibel.lod.core.objects;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.ComplexRenderRegion;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder;
import com.seibel.lod.core.objects.opengl.SimpleRenderRegion;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
public abstract class RenderRegion implements AutoCloseable
{
// target can be Null.
// If return null, means all status updated without switching objects.
@SuppressWarnings("resource")
public static RenderRegion updateStatus(RenderRegion target, LodQuadBuilder builder, RegionPos regPos) {
boolean useSimpleRegion = (builder.getCurrentNeededVertexBuffers() <= 6) || true;
if ((target instanceof SimpleRenderRegion && !useSimpleRegion) ||
target instanceof ComplexRenderRegion && useSimpleRegion) {
target.close();
target = null;
}
if (target == null) {
return useSimpleRegion ?
new SimpleRenderRegion(builder.getCurrentNeededVertexBuffers(), regPos)
: new ComplexRenderRegion(regPos);
}
return null;
}
public abstract void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod);
public abstract boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling);
public abstract void render(LodRenderProgram shaderProgram);
public abstract void debugDumpStats(StatsMap statsMap);
@Override
public abstract void close();
}
@@ -40,8 +40,8 @@ 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.MovabeGridRingList;
import com.seibel.lod.core.util.MovabeGridRingList.Pos;
import com.seibel.lod.core.util.MovableGridRingList;
import com.seibel.lod.core.util.MovableGridRingList.Pos;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.SpamReducedLogger;
import com.seibel.lod.core.util.UnitBytes;
@@ -80,7 +80,7 @@ public class LodDimension
// these three variables are private to force use of the getWidth() method
// which is a safer way to get the width then directly asking the arrays
/** stores all the regions in this dimension */
public MovabeGridRingList<LodRegion> regions;
public MovableGridRingList<LodRegion> regions;
//NOTE: This list pos is relative to center
private volatile RegionPos[] iteratorList = null;
@@ -146,7 +146,7 @@ public class LodDimension
}
regions = new MovabeGridRingList<LodRegion>(halfWidth, 0, 0);
regions = new MovableGridRingList<LodRegion>(halfWidth, 0, 0);
generateIteratorList();
}
@@ -493,29 +493,7 @@ public class LodDimension
});
return posToGenerate;
}
/**
* Fills the posToRender with the position to render for the regionPos given in input
*/
public void getPosToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX,
int playerPosZ)
{
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.BALANCED;
DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getDropoffQuality();
if (dropoffQuality == DropoffQuality.AUTO)
dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ?
DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED;
if (region != null)
region.getPosToRender(posToRender, playerPosX, playerPosZ, generationPriority, dropoffQuality);
}
/**
* Determines how many vertical LODs could be used
* for the given region at the given detail level
@@ -716,7 +694,7 @@ public class LodDimension
width = newWidth;
halfWidth = width/ 2;
Pos p = regions.getCenter();
regions = new MovabeGridRingList<LodRegion>(halfWidth, p.x, p.y);
regions = new MovableGridRingList<LodRegion>(halfWidth, p.x, p.y);
generateIteratorList();
}
@@ -764,6 +742,11 @@ public class LodDimension
@Override
public String toString()
{
return "[Dim = "+dimension.getDimensionName()+", Region = "+regions+"]";
}
public String toDetailString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Dimension : \n");
@@ -32,6 +32,8 @@ import com.seibel.lod.core.util.DataPointUtil;
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.config.ILodConfigWrapperSingleton;
/**
* This object holds all loaded LevelContainers acting as a quad tree for a
@@ -46,6 +48,7 @@ import com.seibel.lod.core.util.LodUtil;
* @version 10-10-2021
*/
public class LodRegion {
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
/** Number of detail level supported by a region */
private static final byte POSSIBLE_LOD = LodUtil.DETAIL_OPTIONS;
@@ -281,6 +284,17 @@ public class LodRegion {
priority, genMode, shouldSort, needFarPos);
}
}
public void getPosToRender(PosToRenderContainer posToRender, int playerPosX,
int playerPosZ)
{
// use FAR_FIRST on local worlds and NEAR_FIRST on servers
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getResolvedGenerationPriority();
DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getResolvedDropoffQuality();
getPosToRender(posToRender, playerPosX, playerPosZ, generationPriority, dropoffQuality);
}
/**
* This method will fill the posToRender array with all levelPos that are
@@ -289,7 +303,7 @@ public class LodRegion {
* TODO why don't we return the posToRender, it would make this easier to
* understand
*/
public void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ,
private void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ,
GenerationPriority priority, DropoffQuality dropoffQuality) {
double minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX, playerPosZ);
byte targetLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
@@ -1,114 +0,0 @@
package com.seibel.lod.core.objects.opengl;
import java.util.TreeMap;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.RenderRegion;
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.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.util.UnitBytes;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
public class ComplexRenderRegion extends RenderRegion {
LodVertexBuffer[] vbos;
final RegionPos regPos;
private static final float FULL_SIZED_BUFFERS =
LodBufferBuilderFactory.MAX_TRIANGLES_PER_BUFFER * LodUtil.LOD_VERTEX_FORMAT.getByteSize();
public ComplexRenderRegion(RegionPos pos) {
vbos = new LodVertexBuffer[1];
regPos = pos;
}
public void resize(int size) {
if (vbos.length != size) {
LodVertexBuffer[] newVbos = new LodVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
vbos[i].close();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (LodVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
public LodVertexBuffer[] debugGetBuffers() {
return vbos;
}
@Override
public void close() {
}
public LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
} else if (vbos[iIndex].isBufferStorage != useBuffStorage) {
vbos[iIndex].close();
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
@Override
public boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling) {
if (enableDirectionalCulling && !RenderUtil.isRegionInViewFrustum(renderer.getCameraBlockPosition(),
renderer.getLookAtVector(), regPos.x, regPos.z)) return false;
return true;
}
@Override
public void render(LodRenderProgram shaderProgram)
{
for (LodVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RegionRegions");
statsMap.incStat("SimpleRegionRegions");
for (LodVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("Buffers");
if (b.size == FULL_SIZED_BUFFERS) {
statsMap.incStat("FullsizedBuffers");
}
statsMap.incBytesStat("TotalUsage", b.size);
}
}
@Override
public void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
// TODO Auto-generated method stub
}
}
@@ -0,0 +1,116 @@
package com.seibel.lod.core.objects.opengl;
import java.util.ConcurrentModificationException;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.StatsMap;
public abstract class RenderBuffer implements AutoCloseable
{
private enum State {
None,
Building,
Uploading,
Closed,
}
private State owner = State.None;
private State nextOwner = State.None;
final private void _lockThread(State newOwner) {
if (owner != State.None || (nextOwner != State.None && nextOwner != newOwner))
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = newOwner;
nextOwner = State.None;
}
final private void _unlockThread(State oldOwner) {
if (owner != oldOwner)
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = State.None;
}
final private void _unlockThreadTo(State oldOwner, State newOwner) {
if (owner != oldOwner)
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = State.None;
nextOwner = newOwner;
}
final public void build(Runnable r) {
_lockThread(State.Building);
r.run();
_unlockThread(State.Building);
}
/* Return false if current renderMethod is not suited for current builder
* This will auto close the object if returning false. */
final public boolean tryUploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod) {
_lockThread(State.Uploading);
boolean successful = uploadBuffers(builder, uploadMethod);
if (!successful) {
_unlockThreadTo(State.Uploading, State.Closed);
close();
return false;
}
_unlockThread(State.Uploading);
return true;
}
// ======================================================================
// ====================== Methods for implementations ===================
// ======================================================================
// =========== Called by build starter thread ==========
/* Called on being reused after the object is swapped to the back
* and a new build event is triggered. Used for cleaning up non
* reusable objects sooner.
* Note: This is ran on BUILDER thread, and does not have access to
* GL Context, Use GLProxy.recordOpenGlCall() to access GL Context
* instead! */
public void onReuse() {}
// =========== Called by buffer upload thread ==========
/* Return false if current renderMethod is not suited for current builder
* If false, close call will be automatically triggered.
* If true, the object will be used (by first calling the swapBufferToFront())
* on tick render. */
protected abstract boolean uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod);
// ========== Called by render thread ==========
/* Called on buffer first being used by a render thread. */
public void onSwapToFront() {}
/* Called on buffer no longer being used. (Life ended)
* Return false if current object cannot be reused.
* Note: This should not do too much stuff as it is ran on render thread!
* The corresponding cleanups should be done using the onReuse() to prevent
* lag spikes! If you want this buffer to not be reused, but cleanup is
* expensive, use onReuse() instead!
* Note 2: This may not be triggered on some siturations like renderer being
* terminated, or dimension changed. So implementation should NEVER assume
* that onSwapToFront() will link to a call of onSwapToBack()! */
public boolean onSwapToBack() {return true;}
/* Called on... well... rendering.
* Return false if nothing rendered. (Optional) */
public abstract boolean render(LodRenderProgram shaderProgram);
// ========== Called by any thread. (thread safe) ==========
/* Called by anyone. This method is allowed to throw exceptions, but
* are never allowed to modify any values. This should behave the same
* to other methods as if the method have never been called.
* Note: This method is PURELY for debug or stats logging ONLY! */
public abstract void debugDumpStats(StatsMap statsMap);
// ========= Called only when 1 thread is using it =======
/* This method is called when object is no longer in use.
* Called either after uploadBuffers() returned false (On buffer Upload
* thread), or by others when the object is not being used. (not in build,
* upload, or render state). */
public abstract void close();
}
@@ -0,0 +1,422 @@
package com.seibel.lod.core.objects.opengl;
import java.util.ConcurrentModificationException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.bufferBuilding.CubicLodTemplate;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.objects.PosToRenderContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
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.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.DataPointUtil;
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.MovableGridList;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
public class RenderRegion implements AutoCloseable
{
public static final boolean ENABLE_EVENT_LOGGING = false;
public static final boolean ENABLE_EVENT_STEP_LOGGING = false;
public static final boolean ENABLE_VERBOSE_LOGGING = false;
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private enum BackState {
Unused,
Building,
Complete,
}
private enum FrontState {
Unused,
Rendering,
Invalidated,
}
final RegionPos regionPos;
RenderBuffer renderBufferBack = null;
AtomicReference<BackState> backState =
new AtomicReference<BackState>(BackState.Unused);
AtomicReference<FrontState> frontState =
new AtomicReference<FrontState>(FrontState.Unused);
RenderBuffer renderBufferFront = null;
final LodDimension lodDim;
public RenderRegion(RegionPos regPos, LodDimension lodDim) {
regionPos = regPos;
this.lodDim = lodDim;
}
public boolean canRender(LodDimension lodDim, RegionPos regPos) {
return lodDim == this.lodDim && regPos.equals(regionPos);
}
public Optional<CompletableFuture<Void>> updateStatus(Executor bufferUploader, Executor bufferBuilder, boolean alwaysRegen, int playerPosX, int playerPosZ) {
BackState state = backState.get();
if (state != BackState.Unused) {
if (ENABLE_VERBOSE_LOGGING) ApiShared.LOGGER.info("{}: UpdateStatus rejected. Cause: BackState is {}", regionPos, state);
return Optional.empty();
}
LodRegion r = lodDim.getRegion(regionPos.x, regionPos.z);
if (r==null) {
if (ENABLE_VERBOSE_LOGGING) ApiShared.LOGGER.info("{}: UpdateStatus rejected. Cause: Region is null", regionPos);
return Optional.empty();
}
if (!alwaysRegen && r.needRegenBuffer == 0) {
if (ENABLE_VERBOSE_LOGGING) ApiShared.LOGGER.info("{}: UpdateStatus rejected. Cause: Region doesn't need regen", regionPos);
return Optional.empty();
}
if (!backState.compareAndSet(BackState.Unused, BackState.Building)) {
if (ENABLE_VERBOSE_LOGGING) ApiShared.LOGGER.info("{}: UpdateStatus rejected. Cause: CAS on BackState failed: ", backState.get());
return Optional.empty();
}
r.needRegenBuffer--;
return Optional.of(startBuid(bufferUploader, bufferBuilder, r, lodDim, playerPosX, playerPosZ));
}
public boolean render(LodDimension renderDim,
Vec3d cameraPos, AbstractBlockPosWrapper cameraBlockPos, Vec3f cameraDir,
Mat4f baseModelViewMatrix, boolean enableDirectionalCulling, LodRenderProgram program) {
if (!frontState.compareAndSet(FrontState.Unused, FrontState.Rendering)) return false;
try {
if (renderDim != lodDim) return false;
if (enableDirectionalCulling &&
!RenderUtil.isRegionInViewFrustum(cameraBlockPos,
cameraDir, regionPos.x, regionPos.z)) return false;
BackState state = backState.get();
if (state == BackState.Complete) {
if (renderBufferBack != null) {
if (ENABLE_EVENT_LOGGING) ApiShared.LOGGER.info("RenderRegion swap @ {}", regionPos);
boolean shouldKeep = renderBufferFront==null ? false : renderBufferFront.onSwapToBack();
RenderBuffer temp = shouldKeep ? renderBufferFront : null;
renderBufferFront = renderBufferBack;
renderBufferBack = temp;
if (renderBufferFront != null) renderBufferFront.onSwapToFront();
}
if (!backState.compareAndSet(BackState.Complete, BackState.Unused)) {
ApiShared.LOGGER.error("RenderRegion.render() got illegal state on swapping buffer!");
}
}
if (renderBufferFront == null) return false;
Mat4f localModelViewMatrix = baseModelViewMatrix.copy();
localModelViewMatrix.multiplyTranslationMatrix(
(regionPos.x * LodUtil.REGION_WIDTH) - cameraPos.x,
LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y,
(regionPos.z * LodUtil.REGION_WIDTH) - cameraPos.z);
program.fillUniformModelMatrix(localModelViewMatrix);
return renderBufferFront.render(program);
} finally {
frontState.compareAndSet(FrontState.Rendering, FrontState.Unused);
}
}
private void recreateBuffer(LodQuadBuilder builder) {
if (renderBufferBack != null) throw new RuntimeException("Assert Error");
boolean useSimpleBuffer = (builder.getCurrentNeededVertexBuffers() <= 6) || true;
renderBufferBack = useSimpleBuffer ?
new SimpleRenderBuffer()
: null; //new ComplexRenderRegion(regPos);
}
private CompletableFuture<Void> startBuid(Executor bufferUploader, Executor bufferBuilder, LodRegion region, LodDimension lodDim, int playerPosX, int playerPosZ) {
if (ENABLE_EVENT_LOGGING) ApiShared.LOGGER.info("RenderRegion startBuild @ {}", regionPos);
LodRegion[] adjRegions = new LodRegion[4];
try {
if (renderBufferBack != null) renderBufferBack.onReuse();
for (LodDirection dir : LodDirection.ADJ_DIRECTIONS) {
adjRegions[dir.ordinal()] = lodDim.getRegion(regionPos.x+dir.getNormal().x, regionPos.z+dir.getNormal().z);
}
} catch (Throwable t) {
region.needRegenBuffer = 2;
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
ApiShared.LOGGER.error("\"Lod Builder Starter\""
+ " encountered error on catching exceptions and fallback on starting build task: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
throw t;
}
return CompletableFuture.supplyAsync(() -> {
try {
if (ENABLE_EVENT_STEP_LOGGING) ApiShared.LOGGER.info("RenderRegion start QuadBuild @ {}", regionPos);
LodQuadBuilder builder = new LodQuadBuilder(10);
Runnable buildRun = ()->{
makeLodRenderData(builder, region, adjRegions, playerPosX, playerPosZ);
};
if (renderBufferBack != null)
renderBufferBack.build(buildRun);
else
buildRun.run();
if (ENABLE_EVENT_STEP_LOGGING) ApiShared.LOGGER.info("RenderRegion end QuadBuild @ {}", regionPos);
return builder;
} catch (Throwable e3) {
ApiShared.LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
throw e3;
}
}, bufferBuilder).thenAcceptAsync((builder) -> {
try {
if (ENABLE_EVENT_STEP_LOGGING) ApiShared.LOGGER.info("RenderRegion start Upload @ {}", regionPos);
GLProxy glProxy = GLProxy.getInstance();
GpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
GLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
try {
if (renderBufferBack == null) recreateBuffer(builder);
if (!renderBufferBack.tryUploadBuffers(builder, method)) {
renderBufferBack = null;
recreateBuffer(builder);
if (!renderBufferBack.tryUploadBuffers(builder, method)) {
throw new RuntimeException("Newly created renderBuffer "
+ "is still returning false on tryUploadBuffers!");
}
}
} finally {
glProxy.setGlContext(oldContext);
}
if (ENABLE_EVENT_STEP_LOGGING) ApiShared.LOGGER.info("RenderRegion end Upload @ {}", regionPos);
if (!backState.compareAndSet(BackState.Building, BackState.Complete)) {
throw new ConcurrentModificationException("RenderRegion Illegal State");
}
} catch (Throwable e3) {
ApiShared.LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
}
}, bufferUploader).exceptionallyCompose((e) -> {
region.needRegenBuffer = 2;
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
ApiShared.LOGGER.error("\"LodNodeBufferBuilder\""
+ " encountered error on exit: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
return CompletableFuture.failedStage(e);
});
}
private static final int ADJACENT8[][] = {
{-1,-1},
{-1, 0},
{-1, 1},
{ 0,-1},
//{ 0, 0},
{ 0, 1},
{ 1,-1},
{ 1, 0},
{ 1, 1}
};
private static MovableGridList<Boolean> shinkGridEdge(MovableGridList<Boolean> target) {
MovableGridList<Boolean> result = new MovableGridList<Boolean>(
target.gridCentreToEdge-1, target.getCenterX(), target.getCenterY());
int chunkGridMinX = target.getCenterX() - target.gridCentreToEdge;
int chunkGridMinZ = target.getCenterY() - target.gridCentreToEdge;
for (int x=chunkGridMinX+1; x<chunkGridMinX+target.gridSize-2; x++) {
for (int z=chunkGridMinZ+1; z<chunkGridMinZ+target.gridSize-2; z++) {
Boolean b = target.get(x+1, z);
boolean rendered = b!=null && b;
if (!rendered) continue;
for (int[] pos : ADJACENT8) {
Boolean b0 = target.get(x+pos[0], z+pos[1]);
rendered &= b0!=null && b0;
}
if (rendered) result.set(x, z, true);
}
}
return result;
}
private static void makeLodRenderData(LodQuadBuilder quadBuilder, LodRegion region, LodRegion[] adjRegions, int playerX,
int playerZ) {
byte minDetail = region.getMinDetailLevel();
// Variable initialization
DebugMode debugMode = CONFIG.client().advanced().debugging().getDebugMode();
// We ask the lod dimension which block we have to render given the player
// position
PosToRenderContainer posToRender = new PosToRenderContainer(minDetail, region.regionPosX, region.regionPosZ);
region.getPosToRender(posToRender, playerX, playerZ);
MovableGridList<Boolean> chunkGrid = ClientApi.renderer.vanillaRenderedChunks;
if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER) {
chunkGrid = shinkGridEdge(chunkGrid);
}
for (int index = 0; index < posToRender.getNumberOfPos(); index++) {
byte detailLevel = posToRender.getNthDetailLevel(index);
int posX = posToRender.getNthPosX(index);
int posZ = posToRender.getNthPosZ(index);
// TODO: In the future, 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
// The block is in the player chunk or in a chunk adjacent to the player
if (detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL) {
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posX);
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posZ);
Boolean isRendered = chunkGrid.get(chunkX, chunkZ);
// skip any chunks that Minecraft is going to render
if (isRendered != null && isRendered) continue;
}
long[] posData = region.getAllData(detailLevel, posX, posZ);
if (posData == null || posData.length == 0 || !DataPointUtil.doesItExist(posData[0])
|| DataPointUtil.isVoid(posData[0]))
continue;
long[][][] adjData = new long[4][1][];
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the
// border when we have transparent block like water or glass
// to avoid having a "darker border" underground
// Arrays.fill(adjShadeDisabled, false);
// We check every adj block in each direction
// If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is
// not
// to always have a wall underwater
for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) {
int xAdj = posX + lodDirection.getNormal().x;
int zAdj = posZ + lodDirection.getNormal().z;
byte adjDetail = detailLevel;
int chunkXAdj = LevelPosUtil.getChunkPos(detailLevel, xAdj);
int chunkZAdj = LevelPosUtil.getChunkPos(detailLevel, zAdj);
Boolean isRenderedAdj = chunkGrid.get(chunkXAdj, chunkZAdj);
boolean adjSkip = isRenderedAdj!=null && isRenderedAdj;
//We check if the adjPos is to be rendered
boolean renderAdjPos = posToRender.contains(detailLevel, xAdj, zAdj);
boolean renderLowerAdjPos = posToRender.contains((byte) (detailLevel-1), xAdj*2, zAdj*2);
LodRegion adjRegion = region;
//since he system doesn't work for region border we need to check with another system
if(!renderAdjPos && !renderLowerAdjPos)
{
//we compute the distance from the adjPos
double minDistance = LevelPosUtil.minDistance(detailLevel, xAdj, zAdj, playerX, playerZ) - 1.4142*(2 << detailLevel);
//we compute at which detail that position should be rendered
adjRegion = adjRegions[lodDirection.ordinal()-2];
byte minLevel;
if(adjRegion != null)
{
minLevel = (byte) Math.max(adjRegion.getMinDetailLevel(),
DetailDistanceUtil.getDetailLevelFromDistance(minDistance));
} else{
minLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
}
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
renderAdjPos = detailLevel == minLevel;
renderLowerAdjPos = detailLevel-1 == minLevel;
}
if (renderAdjPos && !adjSkip) {
//The adj data is at same detail and is extracted
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getAllData(adjDetail, xAdj, zAdj);
} else if (renderLowerAdjPos)
{
//The adj data is at lower detail and is extracted in two steps
xAdj *= 2;
zAdj *= 2;
adjDetail = (byte) (detailLevel - 1);
adjData[lodDirection.ordinal() - 2] = new long[2][];
isRenderedAdj = chunkGrid.get(chunkXAdj, chunkZAdj);
adjSkip = isRenderedAdj!=null && isRenderedAdj;
if (!adjSkip) {
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getAllData(adjDetail, xAdj, zAdj);
}
xAdj += Math.abs(lodDirection.getNormal().x);
zAdj += Math.abs(lodDirection.getNormal().z);
isRenderedAdj = chunkGrid.get(chunkXAdj, chunkZAdj);
adjSkip = isRenderedAdj!=null && isRenderedAdj;
if (!adjSkip)
{
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getAllData(adjDetail, xAdj, zAdj);
}
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < posData.length; i++) {
long data = posData[i];
// If the data is not renderable (Void or non-existing) we stop since there is
// no data left in this position
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
long adjDataTop = i - 1 >= 0 ? posData[i - 1] : DataPointUtil.EMPTY_DATA;
long adjDataBot = i + 1 < posData.length ? posData[i + 1] : DataPointUtil.EMPTY_DATA;
// We send the call to create the vertices
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
LevelPosUtil.getRegionModule(detailLevel, posX),
LevelPosUtil.getRegionModule(detailLevel, posZ), quadBuilder, debugMode);
}
} // for pos to in list to render
// the thread executed successfully
// Merge all quads
quadBuilder.mergeQuads();
}
@Override
public void close()
{
if (renderBufferBack != null) renderBufferBack.close();
while (frontState.get() != FrontState.Invalidated && !frontState.compareAndSet(FrontState.Unused, FrontState.Invalidated)) {
Thread.yield(); //FIXME: If on java 9, use Thread.onSpinWait();
}
if (renderBufferFront != null) renderBufferFront.close();
}
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderRegions");
RenderBuffer front = renderBufferFront;
if (front!=null) {
statsMap.incStat("FrontBuffers");
front.debugDumpStats(statsMap);
}
RenderBuffer back = renderBufferBack;
if (back!=null) {
statsMap.incStat("BackBuffers");
back.debugDumpStats(statsMap);
}
}
}
@@ -8,27 +8,127 @@ import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.RenderRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder.BufferFiller;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
public class SimpleRenderRegion extends RenderRegion {
LodVertexBuffer[] vbos;
final RegionPos regPos;
public class SimpleRenderBuffer extends RenderBuffer
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final int FULL_SIZED_BUFFERS =
LodBufferBuilderFactory.MAX_TRIANGLES_PER_BUFFER * LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3;
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
public SimpleRenderRegion(int size, RegionPos pos) {
vbos = new LodVertexBuffer[size];
regPos = pos;
LodVertexBuffer[] vbos;
// public void onReuse() {}
public SimpleRenderBuffer() {
vbos = new LodVertexBuffer[0];
}
@Override
protected boolean uploadBuffers(LodQuadBuilder builder, GpuUploadMethod method)
{
// if (builder.getCurrentNeededVertexBuffers()>6) return false;
if (method.useEarlyMapping) {
_uploadBuffersMapped(builder, method);
} else {
_uploadBuffersDirect(builder, method);
}
return true;
}
// public void onSwapToFront() {}
// public void onSwapToBack() {}
@Override
public boolean render(LodRenderProgram shaderProgram)
{
for (LodVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
return true;
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (LodVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.size == FULL_SIZED_BUFFERS) {
statsMap.incStat("FullsizedVBOs");
}
statsMap.incBytesStat("TotalUsage", b.size);
}
}
@Override
public void close()
{
GLProxy.getInstance().recordOpenGlCall(() -> {
for (LodVertexBuffer b : vbos) {
b.close();
}
});
}
private void _uploadBuffersDirect(LodQuadBuilder builder, GpuUploadMethod method) {
resize(builder.getCurrentNeededVertexBuffers());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
int i = 0;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
ByteBuffer bb = iter.next();
LodVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage);
int size = bb.limit() - bb.position();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFERS);
if (BPerNS<=0) continue;
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
try {
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
} catch (InterruptedException e) {
}
remainingNS = 0;
}
}
}
private void _uploadBuffersMapped(LodQuadBuilder builder, GpuUploadMethod method)
{
resize(builder.getCurrentNeededVertexBuffers());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new LodVertexBuffer(method.useBufferStorage);
}
BufferFiller func = builder.makeBufferFiller(method);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
}
private LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
private void resize(int size) {
@@ -50,104 +150,4 @@ public class SimpleRenderRegion extends RenderRegion {
vbos = newVbos;
}
}
public LodVertexBuffer[] debugGetBuffers() {
return vbos;
}
@Override
public void close() {
for (LodVertexBuffer b : vbos) {
if (b != null) b.close();
}
vbos = new LodVertexBuffer[0];
}
private LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
private void uploadBuffersViaMapping(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
resize(builder.getCurrentNeededVertexBuffers());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new LodVertexBuffer(uploadMethod.useBufferStorage);
}
BufferFiller func = builder.makeBufferFiller(uploadMethod);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
}
@Override
public void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
if (uploadMethod.useEarlyMapping) {
uploadBuffersViaMapping(builder, uploadMethod);
return;
}
resize(builder.getCurrentNeededVertexBuffers());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
int i = 0;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
ByteBuffer bb = iter.next();
LodVertexBuffer vbo = getOrMakeVbo(i++, uploadMethod.useBufferStorage);
int size = bb.limit() - bb.position();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFERS);
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
if (remainingNS > LodBufferBuilderFactory.MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
remainingNS = LodBufferBuilderFactory.MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
try {
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
} catch (InterruptedException e) {
}
remainingNS = 0;
}
}
}
@Override
public boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling) {
if (enableDirectionalCulling && !RenderUtil.isRegionInViewFrustum(renderer.getCameraBlockPosition(),
renderer.getLookAtVector(), regPos.x, regPos.z)) return false;
return true;
}
@Override
public void render(LodRenderProgram shaderProgram)
{
for (LodVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RegionRegions");
statsMap.incStat("SimpleRegionRegions");
for (LodVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("Buffers");
if (b.size == FULL_SIZED_BUFFERS) {
statsMap.incStat("FullsizedBuffers");
}
statsMap.incBytesStat("TotalUsage", b.size);
}
}
}
@@ -33,13 +33,15 @@ 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.handlers.IReflectionHandler;
import com.seibel.lod.core.objects.RenderRegion;
import com.seibel.lod.core.objects.lod.LodDimension;
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.RenderRegion;
import com.seibel.lod.core.render.objects.LightmapTexture;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MovableGridRingList;
import com.seibel.lod.core.util.MovableGridList;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
@@ -206,16 +208,18 @@ public class LodRenderer
// 3. we aren't waiting for the build and draw buffers to swap
// (this is to prevent thread conflicts)
LagSpikeCatcher swapBuffer = new LagSpikeCatcher();
if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(),
MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), partialRegen, fullRegen)) {
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvailable() is true
fullRegen = false;
partialRegen = false;
if (partialRegen || fullRegen) {
if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(),
MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen)) {
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvailable() is true
fullRegen = false;
partialRegen = false;
}
}
swapBuffer.end("SwapBuffer");
// Get the front buffers to draw
MovableGridList<RenderRegion> regions = lodBufferBuilderFactory.getFrontBuffers();
MovableGridRingList<RenderRegion> regions = lodBufferBuilderFactory.getRenderRegions();
if (regions == null) {
// There is no vbos, which means nothing needs to be drawn. So skip rendering
@@ -310,29 +314,26 @@ public class LodRenderer
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
AbstractBlockPosWrapper cameraBlockPos = MC_RENDER.getCameraBlockPosition();
Vec3f cameraDir = MC_RENDER.getLookAtVector();
{
int ox,oy,dx,dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.gridSize;
int len = regions.getSize();
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 regionX = ox+regions.getCenterX();
int regionZ = oy+regions.getCenterY();
MovableGridRingList.Pos pos = regions.getCenter();
int regionX = ox+pos.x;
int regionZ = oy+pos.y;
{
RenderRegion region = regions.get(regionX, regionZ);
if (region != null && region.shouldRender(MC_RENDER, !cullingDisabled)) {
Mat4f localModelViewMatrix = baseModelViewMatrix.copy();
localModelViewMatrix.multiplyTranslationMatrix(
(regionX * LodUtil.REGION_WIDTH) - cameraPos.x,
LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y,
(regionZ * LodUtil.REGION_WIDTH) - cameraPos.z);
shaderProgram.fillUniformModelMatrix(localModelViewMatrix);
region.render(shaderProgram);
}
if (region == null) continue;
region.render(lodDim, cameraPos, cameraBlockPos, cameraDir,
baseModelViewMatrix, !cullingDisabled, shaderProgram);
}
}
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
@@ -447,4 +447,12 @@ public class LodUtil
if (freeMem/(double)maxMem < minFreeMemoryPercent) return false;
return true;
}
public static void checkInterrupts() throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
}
public static void checkInterruptsUnchecked() {
if (Thread.interrupted()) throw new RuntimeException(new InterruptedException());
}
}
@@ -20,6 +20,17 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
public final int gridCentreToEdge;
public final int gridSize;
/*
* WARNING: Not yet tested if its atomic. (non Thread safe)
*/
public MovableGridList(MovableGridList<T> other) {
super(other);
centerX = other.centerX;
centerY = other.centerY;
gridCentreToEdge = other.gridCentreToEdge;
gridSize = other.gridSize;
}
public MovableGridList(int gridCentreToEdge, int centerX, int centerY) {
super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1));
gridSize = gridCentreToEdge * 2 + 1;
@@ -6,7 +6,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
public class MovabeGridRingList<T> extends ArrayList<T> implements List<T> {
public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
private static final long serialVersionUID = -7743190533384530134L;
@@ -22,7 +22,7 @@ public class MovabeGridRingList<T> extends ArrayList<T> implements List<T> {
private final int size;
private final ReentrantReadWriteLock moveLock = new ReentrantReadWriteLock();
public MovabeGridRingList(int halfSize, int centerX, int centerY) {
public MovableGridRingList(int halfSize, int centerX, int centerY) {
super((halfSize * 2 + 1) * (halfSize * 2 + 1));
size = halfSize * 2 + 1;
this.halfSize = halfSize;
@@ -188,7 +188,7 @@ public class MovabeGridRingList<T> extends ArrayList<T> implements List<T> {
// the total width, just delete the current data
// and update the pos
if (Math.abs(deltaX) >= size || Math.abs(deltaY) >= size) {
clear();
clear(d);
} else {
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
@@ -36,7 +36,9 @@ 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.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
/**
* This holds the config defaults, setters/getters
@@ -157,6 +159,14 @@ public interface ILodConfigWrapperSingleton
+ " or "+ DropoffQuality.PERFORMANCE_FOCUSED +" otherwise. \n";
DropoffQuality getDropoffQuality();
void setDropoffQuality(DropoffQuality newDropoffQuality);
default DropoffQuality getResolvedDropoffQuality() {
DropoffQuality dropoffQuality = getDropoffQuality();
if (dropoffQuality == DropoffQuality.AUTO)
dropoffQuality = getLodChunkRenderDistance() < 128 ?
DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED;
return dropoffQuality;
}
}
interface IFogQuality
@@ -417,6 +427,14 @@ public interface ILodConfigWrapperSingleton
GenerationPriority getGenerationPriority();
void setGenerationPriority(GenerationPriority newGenerationPriority);
default GenerationPriority getResolvedGenerationPriority() {
GenerationPriority priority = getGenerationPriority();
IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
if (priority == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.BALANCED;
return priority;
}
BlocksToAvoid BLOCKS_TO_AVOID_DEFAULT = BlocksToAvoid.BOTH;
String BLOCKS_TO_AVOID_DESC = ""
+ " When generating fake chunks, what blocks should be ignored? \n"