Overhaul Rendering Buffer Management
+ Merging with Leo's fix to adjData and stuff
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+110
-110
@@ -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;
|
||||
|
||||
+3
-3
@@ -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++) {
|
||||
+18
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user