diff --git a/src/main/java/com/seibel/lod/core/objects/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/RenderRegion.java deleted file mode 100644 index abdb2627e..000000000 --- a/src/main/java/com/seibel/lod/core/objects/RenderRegion.java +++ /dev/null @@ -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(); - - - - -} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java index 9a0035ccb..aef36b536 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java @@ -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 regions; + public MovableGridRingList regions; //NOTE: This list pos is relative to center private volatile RegionPos[] iteratorList = null; @@ -146,7 +146,7 @@ public class LodDimension } - regions = new MovabeGridRingList(halfWidth, 0, 0); + regions = new MovableGridRingList(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(halfWidth, p.x, p.y); + regions = new MovableGridRingList(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"); diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java index 2009db599..cce4c68f6 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java @@ -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); diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java deleted file mode 100644 index 55107357a..000000000 --- a/src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java +++ /dev/null @@ -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 backState = + new AtomicReference(BackState.Unused); + AtomicReference frontState = + new AtomicReference(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> 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 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 shinkGridEdge(MovableGridList target) { + MovableGridList result = new MovableGridList( + 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 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); + } + + + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderBuffer.java similarity index 64% rename from src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderRegion.java rename to src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderBuffer.java index 9e0aba044..0c8e5a2c7 100644 --- a/src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderRegion.java +++ b/src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderBuffer.java @@ -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 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 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); - } - } - } diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java index fa2561437..96ff23384 100644 --- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java @@ -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 regions = lodBufferBuilderFactory.getFrontBuffers(); + MovableGridRingList 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))){ diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java index 9630f54f1..43f40369d 100644 --- a/src/main/java/com/seibel/lod/core/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -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()); + } } diff --git a/src/main/java/com/seibel/lod/core/util/MovableGridList.java b/src/main/java/com/seibel/lod/core/util/MovableGridList.java index e26b31ace..6d2326d66 100644 --- a/src/main/java/com/seibel/lod/core/util/MovableGridList.java +++ b/src/main/java/com/seibel/lod/core/util/MovableGridList.java @@ -20,6 +20,17 @@ public class MovableGridList extends ArrayList implements List { public final int gridCentreToEdge; public final int gridSize; + /* + * WARNING: Not yet tested if its atomic. (non Thread safe) + */ + public MovableGridList(MovableGridList 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; diff --git a/src/main/java/com/seibel/lod/core/util/MovabeGridRingList.java b/src/main/java/com/seibel/lod/core/util/MovableGridRingList.java similarity index 97% rename from src/main/java/com/seibel/lod/core/util/MovabeGridRingList.java rename to src/main/java/com/seibel/lod/core/util/MovableGridRingList.java index 52e1d63e8..e12750fa0 100644 --- a/src/main/java/com/seibel/lod/core/util/MovabeGridRingList.java +++ b/src/main/java/com/seibel/lod/core/util/MovableGridRingList.java @@ -6,7 +6,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; -public class MovabeGridRingList extends ArrayList implements List { +public class MovableGridRingList extends ArrayList implements List { private static final long serialVersionUID = -7743190533384530134L; @@ -22,7 +22,7 @@ public class MovabeGridRingList extends ArrayList implements List { 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 extends ArrayList implements List { // 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++) { diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java index b7c0f20e9..994d1771e 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java @@ -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"