diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java index c030a9680..e75ea3726 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java @@ -19,148 +19,46 @@ package com.seibel.lod.core.builders.bufferBuilding; -import java.util.Map; - -import com.seibel.lod.core.enums.LodDirection; import com.seibel.lod.core.enums.rendering.DebugMode; -import com.seibel.lod.core.objects.VertexOptimizer; -import com.seibel.lod.core.objects.opengl.LodBufferBuilder; -import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.objects.opengl.LodBox; +import com.seibel.lod.core.objects.opengl.LodQuadBuilder; import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.LevelPosUtil; import com.seibel.lod.core.util.LodUtil; -import static com.seibel.lod.core.builders.lodBuilding.LodBuilder.MIN_WORLD_HEIGHT; - - - /** * Builds LODs as rectangular prisms. + * * @author James Seibel * @version 12-8-2021 */ -public class CubicLodTemplate -{ - - public static void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerZ, long data, Map adjData, - byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled - )//, int cullingRangeX, int cullingRangeZ) - { - if (vertexOptimizer == null) +public class CubicLodTemplate { + + public static void addLodToBuffer(long data, long topData, long botData, long[][] adjData, byte detailLevel, + int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder, DebugMode debugging) { + short width = (short) (1 << detailLevel); + short x = (short) LevelPosUtil.convert(detailLevel, offsetPosX, LodUtil.BLOCK_DETAIL_LEVEL); + short y = DataPointUtil.getDepth(data); + short z = (short) LevelPosUtil.convert(detailLevel, offsetOosZ, LodUtil.BLOCK_DETAIL_LEVEL); + short dy = (short) (DataPointUtil.getHeight(data) - y); + if (dy == 0) return; - - // equivalent to 2^detailLevel - int blockWidth = 1 << detailLevel; - + int color; - if (debugging != DebugMode.OFF && debugging != DebugMode.SHOW_WIREFRAME) - { + if (debugging != DebugMode.OFF && debugging != DebugMode.SHOW_WIREFRAME) { if (debugging == DebugMode.SHOW_DETAIL || debugging == DebugMode.SHOW_DETAIL_WIREFRAME) color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB(); - else ///if (debugging == DebugMode.SHOW_GENMODE || debugging == DebugMode.SHOW_GENMODE_WIREFRAME) + else /// if (debugging == DebugMode.SHOW_GENMODE || debugging == + /// DebugMode.SHOW_GENMODE_WIREFRAME) color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[DataPointUtil.getGenerationMode(data)].getRGB(); - } - else + } else color = DataPointUtil.getColor(data); - - - generateBoundingBox( - vertexOptimizer, - DataPointUtil.getHeight(data), - DataPointUtil.getDepth(data), - blockWidth, - posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset - playerX, - playerZ, - adjData, - color, - DataPointUtil.getLightSky(data), - DataPointUtil.getLightBlock(data), - adjShadeDisabled); - - addBoundingBoxToBuffer(buffer, vertexOptimizer);//, cullingRangeX, cullingRangeZ); + + LodBox.addBoxQuadsToBuilder(quadBuilder, // buffer + width, dy, width, // setWidth + x, y, z, // setOffset + color, // setColor + DataPointUtil.getLightSky(data), DataPointUtil.getLightBlock(data), // setLights + topData, botData, adjData); // setAdjData } - - /** add the given position and color to the buffer */ - public static void addPosAndColor(LodBufferBuilder buffer, - float x, float y, float z, - int color, byte skyLightValue, byte blockLightValue) - { - // TODO transparency re-add by replacing the color 255 with "ColorUtil.getAlpha(color)" - buffer.position(x, y, z) - .color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255) - .minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue) - .endVertex(); - } - - - - private static void generateBoundingBox(VertexOptimizer vertexOptimizer, - int height, int depth, int width, - double xOffset, double yOffset, double zOffset, - int playerX, int playerZ, - Map adjData, - int color, byte skyLight, byte blockLight, - boolean[] adjShadeDisabled) - { - // don't add an LOD if it is empty - if (height == -1 && depth == -1) - return; - - if (depth == height) - // if the top and bottom points are at the same height - // render this LOD as 1 block thick - height++; - - // offset the AABB by its x/z position in the world since - // it uses doubles to specify its location, unlike the model view matrix - // which only uses floats - double x = -playerX; - double z = -playerZ; - vertexOptimizer.reset(); - vertexOptimizer.setColor(color, adjShadeDisabled); - vertexOptimizer.setLights(skyLight, blockLight); - vertexOptimizer.setWidth(width, height - depth, width); - vertexOptimizer.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z)); - vertexOptimizer.setAdjData(adjData); - } - - private static void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)//, int cullingRangeX, int cullingRangeZ) - { - int color; - byte skyLight; - byte blockLight; - - for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS) - { - //if(vertexOptimizer.isCulled(lodDirection)) - // continue; - // culling - // FIXME: Reimpl backface culling - /* - if (lodDirection == LodDirection.NORTH && vertexOptimizer.getZ(lodDirection, 0) < -cullingRangeZ - || lodDirection == LodDirection.EAST && vertexOptimizer.getX(lodDirection, 0) > cullingRangeX - || lodDirection == LodDirection.SOUTH && vertexOptimizer.getZ(lodDirection, 0) > cullingRangeZ - || lodDirection == LodDirection.WEST && vertexOptimizer.getX(lodDirection, 0) < -cullingRangeX) - continue; - */ - - int verticalFaceIndex = 0; - while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex)) - { - for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++) - { - skyLight = vertexOptimizer.getSkyLight(lodDirection, verticalFaceIndex); - blockLight = (byte) vertexOptimizer.getBlockLight(); - color = vertexOptimizer.getColor(lodDirection); - addPosAndColor(buffer, - vertexOptimizer.getX(lodDirection, vertexIndex), - vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + MIN_WORLD_HEIGHT, - vertexOptimizer.getZ(lodDirection, vertexIndex), - color, skyLight, blockLight ); - } - verticalFaceIndex++; - } - } - } - } diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java index 066bef8da..f0919703d 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java @@ -22,11 +22,9 @@ package com.seibel.lod.core.builders.bufferBuilding; import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -45,16 +43,15 @@ 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.VertexOptimizer; 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.opengl.LodBufferBuilder; +import com.seibel.lod.core.objects.opengl.LodQuadBuilder; import com.seibel.lod.core.objects.opengl.LodVertexBuffer; +import com.seibel.lod.core.objects.opengl.RenderRegion; import com.seibel.lod.core.render.GLProxy; import com.seibel.lod.core.render.LodRenderer; 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.LodThreadFactory; import com.seibel.lod.core.util.LodUtil; @@ -66,223 +63,207 @@ import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; /** - * This object creates the buffers that are - * rendered by the LodRenderer. + * This object creates the buffers that are rendered by the LodRenderer. * * @author James Seibel * @version 12-9-2021 */ -public class LodBufferBuilderFactory -{ - - //TODO: Do some Perf logging of Buffer Building +public class LodBufferBuilderFactory { + + // TODO: Do some Perf logging of Buffer Building public static final boolean ENABLE_BUFFER_PERF_LOGGING = false; public static final boolean ENABLE_BUFFER_SWAP_LOGGING = true; public static final boolean ENABLE_BUFFER_UPLOAD_LOGGING = false; public static final boolean ENABLE_LAG_SPIKE_LOGGING = false; public static final long LAG_SPIKE_THRESOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS); - + public static class LagSpikeCatcher { long timer = System.nanoTime(); - public LagSpikeCatcher() {} + + public LagSpikeCatcher() { + } + public void end(String source) { - if (!ENABLE_LAG_SPIKE_LOGGING) return; + if (!ENABLE_LAG_SPIKE_LOGGING) + return; timer = System.nanoTime() - timer; if (timer > LAG_SPIKE_THRESOLD_NS) { - ClientApi.LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!"); + ClientApi.LOGGER.info("LagSpikeCatcher: " + source + " took " + Duration.ofNanos(timer) + "!"); } } } - + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); - + /** The thread used to generate new LODs off the main thread. */ - private static LodThreadFactory mainGenThreadFactory = - new LodThreadFactory(LodBufferBuilderFactory.class.getSimpleName() + " - main", Thread.NORM_PRIORITY-2); + private static LodThreadFactory mainGenThreadFactory = new LodThreadFactory( + LodBufferBuilderFactory.class.getSimpleName() + " - main", Thread.NORM_PRIORITY - 2); public static ExecutorService mainGenThread = Executors.newSingleThreadExecutor(mainGenThreadFactory); - + /** The threads used to generate buffers. */ - private static LodThreadFactory bufferBuilderThreadFactory = - new LodThreadFactory("BufferBuilder", Thread.NORM_PRIORITY-2); - public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), - bufferBuilderThreadFactory); + private static LodThreadFactory bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder", + Thread.NORM_PRIORITY - 2); + public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool( + CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), bufferBuilderThreadFactory); /** The thread used to upload buffers. */ - private static LodThreadFactory bufferUploadThreadFactory = - new LodThreadFactory(LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY-1); - public static ExecutorService bufferUploadThread = Executors.newSingleThreadExecutor( - bufferUploadThreadFactory); - + private static LodThreadFactory bufferUploadThreadFactory = new LodThreadFactory( + LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY - 1); + public static ExecutorService bufferUploadThread = Executors.newSingleThreadExecutor(bufferUploadThreadFactory); + public static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); - + /** - * When uploading to a buffer that is too small, - * recreate it this many times bigger than the upload payload + * When uploading to a buffer that is too small, recreate it this many times + * bigger than the upload payload */ public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3; - + /** * When buffers are first created they are allocated to this size (in Bytes). * This size will be too small, more than likely. The buffers will be expanded * when need be to fit the larger sizes. */ - public static final int DEFAULT_MEMORY_ALLOCATION = (LodUtil.LOD_VERTEX_FORMAT.getByteSize()*3)*8; - public static final int MAX_TRIANGLES_PER_BUFFER = (1024*1024*1) / (LodUtil.LOD_VERTEX_FORMAT.getByteSize()*3); - - - + public static final int DEFAULT_MEMORY_ALLOCATION = (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3) * 8; + public static final int MAX_TRIANGLES_PER_BUFFER = (1024 * 1024 * 1) + / (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3); + public static int skyLightPlayer = 15; - + /** * How many buffers there are for the given region.
- * This is done because some regions may require more memory than - * can be directly allocated, so we split the regions into smaller sections.
+ * This is done because some regions may require more memory than can be + * directly allocated, so we split the regions into smaller sections.
* This keeps track of those sections. */ // TODO: Check why this is unused - //public volatile int[][] numberOfBuffersPerRegion; - - /** Stores the vertices when building the VBOs */ - // FIXME: Use special warparound type of movable grid list in the future - public volatile MovableGridList buildableBuffers; - + // public volatile int[][] numberOfBuffersPerRegion; + /** Used when building new VBOs */ - public volatile MovableGridList buildableVbos; + public volatile MovableGridList buildableVbos; public volatile int buildableCenterBlockX; public volatile int buildableCenterBlockY; public volatile int buildableCenterBlockZ; /** VBOs that are sent over to the LodNodeRenderer */ - public volatile MovableGridList drawableVbos; + public volatile MovableGridList drawableVbos; public volatile int drawableCenterBlockX; public volatile int drawableCenterBlockY; public volatile int drawableCenterBlockZ; /** - * if this is true the LOD buffers need to be reset and - * the Renderer should call the lodGenBuffers nomatter it - * should have been a full or partial regen or not + * if this is true the LOD buffers need to be reset and the Renderer should call + * the lodGenBuffers nomatter it should have been a full or partial regen or not */ public volatile boolean frontBufferRequireReset = false; public volatile boolean allBuffersRequireReset = false; - + /** - * if this is true the LOD buffers are currently being - * regenerated. + * if this is true the LOD buffers are currently being regenerated. */ public boolean generatingBuffers = false; - /** - * if this is true new LOD buffers have been generated - * and are waiting to be swapped with the drawable buffers + * if this is true new LOD buffers have been generated and are waiting to be + * swapped with the drawable buffers */ private boolean switchVbos = false; // The hideFrontBuffer is for when switching dimensions private volatile boolean hideFrontBuffer = false; private volatile boolean hideBackBuffer = false; - + /** Size of the buffer builders in bytes last time we created them */ public int previousBufferSize = 0; - + /** Width of the dimension in regions last time we created the buffers */ public int previousRegionWidth = 0; - - /** this is used to prevent multiple threads creating, destroying, or using the buffers at the same time */ + + /** + * this is used to prevent multiple threads creating, destroying, or using the + * buffers at the same time + */ private final ReentrantLock bufferLock = new ReentrantLock(); - - private MovableGridList vertexOptimizerCache; + private MovableGridList setsToRender; - + private int lastX = 0; private int lastZ = 0; - - public LodBufferBuilderFactory() - { - + + public LodBufferBuilderFactory() { + } - + /** - * Create a thread to asynchronously generate LOD buffers - * centered around the given camera X and Z. - *
- * This method will write to the drawable near and far buffers. - *
- * After the buildable buffers have been generated they must be - * swapped with the drawable buffers in the LodRenderer to be drawn. + * Create a thread to asynchronously generate LOD buffers centered around the + * given camera X and Z.
+ * This method will write to the drawable near and far buffers.
+ * After the buildable buffers have been generated they must be swapped with the + * drawable buffers in the LodRenderer to be drawn. + * * @return whether it has started a generation task or is blocked */ - public boolean updateAndSwapLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, - int playerX, int playerY, int playerZ, boolean partialRegen, boolean flushBuffers) - { - + public boolean updateAndSwapLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY, + int playerZ, boolean partialRegen, boolean flushBuffers) { + // only allow one generation process to happen at a time if (generatingBuffers) return false; - + if (MC.getCurrentLightMap() == null) // the lighting hasn't loaded yet return false; allBuffersRequireReset |= flushBuffers; - + boolean fullRegen; if (switchVbos) { fullRegen = swapBuffers(); } else { fullRegen = allBuffersRequireReset || frontBufferRequireReset; } - - if (!fullRegen && !partialRegen) return false; - + + if (!fullRegen && !partialRegen) + return false; + generatingBuffers = true; - - Runnable thread = () -> generateLodBuffersThread( - renderer, lodDim, playerX, playerY, playerZ, fullRegen); - + + Runnable thread = () -> generateLodBuffersThread(renderer, lodDim, playerX, playerY, playerZ, fullRegen); + mainGenThread.execute(thread); return true; } - - - - - private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim, - int playerX, int playerY, int playerZ, boolean fullRegen) - { + + private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY, + int playerZ, boolean fullRegen) { bufferLock.lock(); - + long startTime = System.currentTimeMillis(); - ArrayList posToCleanup = new ArrayList(); + ArrayList posToCleanup = new ArrayList(); ArrayList> nodeToRenderThreads = new ArrayList>(); - - try - { + + try { // round the player's block position down to the nearest chunk BlockPos - int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.REGION_DETAIL_LEVEL); - int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.REGION_DETAIL_LEVEL); + int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerX, LodUtil.REGION_DETAIL_LEVEL); + int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerZ, LodUtil.REGION_DETAIL_LEVEL); int renderRange; int vboX; int vboY; int vboZ; - boolean tooFar = Math.abs(buildableCenterBlockX-playerX)+Math.abs(buildableCenterBlockZ-playerZ)>100_000; - - if (fullRegen || tooFar || buildableBuffers==null || buildableVbos==null - || setsToRender==null || vertexOptimizerCache==null) { + boolean tooFar = Math.abs(buildableCenterBlockX - playerX) + + Math.abs(buildableCenterBlockZ - playerZ) > 100_000; + + if (fullRegen || tooFar || buildableVbos == null || setsToRender == null) { if (buildableVbos != null) { - buildableVbos.clear((bs) -> { - for (LodVertexBuffer b : bs) if (b!=null) b.close(); - }); + buildableVbos.clear(RenderRegion::close); } - - renderRange = lodDim.getWidth()/2; //get lodDim half width - buildableBuffers = new MovableGridList(renderRange, playerRegionX, playerRegionZ); - buildableVbos = new MovableGridList(renderRange, playerRegionX, playerRegionZ); + + renderRange = lodDim.getWidth() / 2; // get lodDim half width + buildableVbos = new MovableGridList(renderRange, playerRegionX, playerRegionZ); setsToRender = new MovableGridList(renderRange, playerRegionX, playerRegionZ); - vertexOptimizerCache = new MovableGridList(renderRange, playerRegionX, playerRegionZ); // this will be the center of the VBOs once they have been built - // FIXME: Currently this will drift apart from player pos if there has not been a fullRegen for a while + // FIXME: Currently this will drift apart from player pos if there has not been + // a fullRegen for a while buildableCenterBlockX = playerX; buildableCenterBlockY = playerY; buildableCenterBlockZ = playerZ; @@ -290,44 +271,43 @@ public class LodBufferBuilderFactory vboY = playerY; vboZ = playerZ; } else { - renderRange = buildableBuffers.gridCentreToEdge; + renderRange = buildableVbos.gridCentreToEdge; vboX = buildableCenterBlockX; vboY = buildableCenterBlockY; vboZ = buildableCenterBlockZ; - buildableBuffers.move(playerRegionX, playerRegionZ); - buildableVbos.move(playerRegionX, playerRegionZ, (bs) -> { - for (LodVertexBuffer b : bs) if (b!=null) b.close(); - }); + buildableVbos.move(playerRegionX, playerRegionZ, RenderRegion::close); setsToRender.move(playerRegionX, playerRegionZ); - vertexOptimizerCache.move(playerRegionX, playerRegionZ); } posToCleanup.ensureCapacity(buildableVbos.size()); nodeToRenderThreads.ensureCapacity(buildableVbos.size()); - - //================================// + + // ================================// // create the nodeToRenderThreads // - //================================// - + // ================================// + skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerX, playerY, playerZ); - //int minCullingRange = SingletonHandler.get(ILodConfigWrapperSingleton.class).client().graphics().advancedGraphics().getBacksideCullingRange(); - //int cullingRangeX = Math.max((int)(1.5 * Math.abs(lastX - playerX)), minCullingRange); - //int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)), minCullingRange); + // int minCullingRange = + // SingletonHandler.get(ILodConfigWrapperSingleton.class).client().graphics().advancedGraphics().getBacksideCullingRange(); + // int cullingRangeX = Math.max((int)(1.5 * Math.abs(lastX - playerX)), + // minCullingRange); + // int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)), + // minCullingRange); lastX = playerX; lastZ = playerZ; - List> futuresBuffer = new LinkedList>(); - for (int indexX = 0; indexX < buildableVbos.gridSize; indexX++) - { - for (int indexZ = 0; indexZ < buildableVbos.gridSize; indexZ++) - { + List> futuresBuffer = new LinkedList>(); + for (int indexX = 0; indexX < buildableVbos.gridSize; indexX++) { + for (int indexZ = 0; indexZ < buildableVbos.gridSize; indexZ++) { final int regionX = indexX + buildableVbos.getCenterX() - buildableVbos.gridCentreToEdge; final int regionZ = indexZ + buildableVbos.getCenterY() - buildableVbos.gridCentreToEdge; - + boolean needRegen = lodDim.getAndClearRegionNeedBufferRegen(regionX, regionZ); needRegen |= fullRegen; - if (!needRegen) continue; - + if (!needRegen) + continue; + LodRegion region = lodDim.getRegion(regionX, regionZ); - if (region == null) continue; + if (region == null) + continue; RegionPos regionPos = new RegionPos(regionX, regionZ); posToCleanup.add(regionPos); @@ -335,45 +315,50 @@ public class LodBufferBuilderFactory byte minDetail = region.getMinDetailLevel(); final int pX = playerX; final int pZ = playerZ; - - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - LodBufferBuilder builder = buildableBuffers.get(regionX, regionZ); - if (builder == null) { - builder = buildableBuffers.setAndGet(regionX, regionZ, new LodBufferBuilder(DEFAULT_MEMORY_ALLOCATION)); - } else { - builder.reset(); - } - builder.begin(GL32.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT); - return makeLodRenderData(lodDim, regionPos, pX, pZ, vboX, vboZ, minDetail);//, cullingRangeX, cullingRangeZ); - }, bufferUploadThread) - .whenCompleteAsync((regPos, e) -> { - LodBufferBuilder buffer = buildableBuffers.get(regPos.x, regPos.z); - if (buffer == null) return; - try { - buffer.end(); - } catch (Exception e2) { - ClientApi.LOGGER.error("\"LodNodeBufferBuilder\" was unable to close buildable buffer: ", e2); - } - if (e!=null) return; - try { - uploadBuffers(regPos); - } catch (Exception e3) { - ClientApi.LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); - } finally { - buildableBuffers.set(regPos.x, regPos.z, null); - } - }, bufferUploadThread); + + class ResultPair { + final LodQuadBuilder quadBuilder; + final RegionPos regionPos; + + ResultPair(LodQuadBuilder quadBuilder, RegionPos regionPos) { + this.quadBuilder = quadBuilder; + this.regionPos = regionPos; + } + + LodQuadBuilder quadBuilder() { + return quadBuilder; + } + + RegionPos regionPos() { + return regionPos; + } + } + + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + LodQuadBuilder quadBuilder = new LodQuadBuilder(6); + makeLodRenderData(quadBuilder, lodDim, regionPos, pX, pZ, vboX, vboZ, minDetail); + return new ResultPair(quadBuilder, regionPos); + }, bufferUploadThread).whenCompleteAsync((result, e) -> { + if (e != null) + return; + try { + uploadBuffers(result.quadBuilder, result.regionPos); + } catch (Exception e3) { + ClientApi.LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); + } + }, bufferUploadThread); futuresBuffer.add(future); } // region z } // region z - //================================// + // ================================// // execute the nodeToRenderThreads // - //================================// - + // ================================// + long executeStart = System.currentTimeMillis(); // wait for all threads to finish - CompletableFuture allFutures = CompletableFuture.allOf(futuresBuffer.toArray(new CompletableFuture[futuresBuffer.size()])); + CompletableFuture allFutures = CompletableFuture + .allOf(futuresBuffer.toArray(new CompletableFuture[futuresBuffer.size()])); try { allFutures.get(60, TimeUnit.SECONDS); } catch (TimeoutException te) { @@ -382,272 +367,217 @@ public class LodBufferBuilderFactory bufferUploadThreadFactory.dumpAllThreadStacks(); bufferBuilderThreads.shutdownNow(); bufferUploadThread.shutdownNow(); - bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder", Thread.NORM_PRIORITY-2); - bufferBuilderThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), + bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder", Thread.NORM_PRIORITY - 2); + bufferBuilderThreads = Executors.newFixedThreadPool( + CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), bufferBuilderThreadFactory); - bufferUploadThreadFactory = new LodThreadFactory(LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY-1); + bufferUploadThreadFactory = new LodThreadFactory( + LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY - 1); bufferUploadThread = Executors.newSingleThreadExecutor(bufferUploadThreadFactory); return; } catch (Exception e) { ClientApi.LOGGER.error("LodBufferBuilder ran into trouble: ", e); } long executeEnd = System.currentTimeMillis(); - + long endTime = System.currentTimeMillis(); long buildTime = endTime - startTime; long executeTime = executeEnd - executeStart; if (ENABLE_BUFFER_PERF_LOGGING) - ClientApi.LOGGER.info("Thread Build&Upload("+nodeToRenderThreads.size()+"/"+(lodDim.getWidth()*lodDim.getWidth())+ (fullRegen ? "FULL" : "")+") time: " + buildTime + " ms" + '\n' + - "thread execute time: " + executeTime + " ms"); + ClientApi.LOGGER.info("Thread Build&Upload(" + nodeToRenderThreads.size() + "/" + + (lodDim.getWidth() * lodDim.getWidth()) + (fullRegen ? "FULL" : "") + ") time: " + buildTime + + " ms" + '\n' + "thread execute time: " + executeTime + " ms"); // mark that the buildable buffers as ready to swap switchVbos = true; - } - catch (Exception e) - { + } catch (Exception e) { ClientApi.LOGGER.error("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ", e); - } - finally - { + } finally { // regardless of whether we were able to successfully create // the buffers, we are done generating. generatingBuffers = false; bufferLock.unlock(); } } - - private static final ThreadLocal tLocalVertexOptimizer = ThreadLocal.withInitial(VertexOptimizer::new); - private static HashMap makeAdjData(int verticalData) { - HashMap map = new HashMap<>(); - map.put(LodDirection.UP, new long[1]); - map.put(LodDirection.DOWN, new long[1]); - for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) - map.put(lodDirection, new long[verticalData]); - return map; - } - private static final ThreadLocal> tLocalAdjData = new ThreadLocal>(); - - private RegionPos makeLodRenderData(LodDimension lodDim, RegionPos regPos, int playerX, int playerZ, - int vboX, int vboZ, byte minDetail) {//, int cullingRangeX, int cullingRangeZ) { - //Variable initialization - int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.CHUNK_DETAIL_LEVEL); - int playerChunkZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.CHUNK_DETAIL_LEVEL); + private RegionPos makeLodRenderData(LodQuadBuilder quadBuilder, LodDimension lodDim, RegionPos regPos, int playerX, + int playerZ, int vboX, int vboZ, byte minDetail) {// , int cullingRangeX, int cullingRangeZ) { + + // Variable initialization + int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerX, LodUtil.CHUNK_DETAIL_LEVEL); + int playerChunkZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerZ, LodUtil.CHUNK_DETAIL_LEVEL); DebugMode debugMode = CONFIG.client().advanced().debugging().getDebugMode(); - VertexOptimizer vertexOptimizer = tLocalVertexOptimizer.get(); - boolean[] adjShadeDisabled = new boolean[VertexOptimizer.DIRECTIONS.length]; - LodBufferBuilder currentBuffer = buildableBuffers.get(regPos.x, regPos.z); - - // determine how many LODs we can stack vertically - int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0); - - //we get or create the map that will contain the adj data - Map adjData = tLocalAdjData.get(); - if (adjData==null || adjData.get(LodDirection.NORTH).length != maxVerticalData) { - adjData = makeAdjData(maxVerticalData); - tLocalAdjData.set(adjData); - } - //We ask the lod dimension which block we have to render given the player position + // We ask the lod dimension which block we have to render given the player + // position PosToRenderContainer posToRender = setsToRender.get(regPos.x, regPos.z); - //previous setToRender cache + // previous setToRender cache if (posToRender == null) { - posToRender = setsToRender.setAndGet(regPos.x, regPos.z, new PosToRenderContainer(minDetail, regPos.x, regPos.z)); + posToRender = setsToRender.setAndGet(regPos.x, regPos.z, + new PosToRenderContainer(minDetail, regPos.x, regPos.z)); } posToRender.clear(minDetail, regPos.x, regPos.z); lodDim.getPosToRender(posToRender, regPos, playerX, playerZ); - - for (int index = 0; index < posToRender.getNumberOfPos(); index++) - { + + for (int index = 0; index < posToRender.getNumberOfPos(); index++) { + byte detailLevel = posToRender.getNthDetailLevel(index); int posX = posToRender.getNthPosX(index); int posZ = posToRender.getNthPosZ(index); - + + long[] posData = lodDim.getAllData(detailLevel, posX, posZ); + if (posData == null || posData.length == 0 || !DataPointUtil.doesItExist(posData[0]) + || DataPointUtil.isVoid(posData[0])) + continue; + long[][] adjData = new long[4][]; + int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX; int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ; - - // TODO: In the future, We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide! + + // 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 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 && - isThisPositionGoingToBeRendered(LevelPosUtil.getChunkPos(detailLevel, posX), - LevelPosUtil.getChunkPos(detailLevel, posZ))) - { + if (detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL && isThisPositionGoingToBeRendered( + LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ))) { continue; } - - //we check if the block to render is not in player chunk + + // we check if the block to render is not in player chunk boolean posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0); - + // 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 + + // 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 - for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) - { - int xAdj = posX + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x; - int zAdj = posZ + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z; + // Arrays.fill(adjShadeDisabled, false); + + // We check every adj block in each direction + for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) { + int xAdj = posX + lodDirection.getNormal().x; + int zAdj = posZ + lodDirection.getNormal().z; chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkX; chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkZ; boolean adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0); - - //If the adj block is rendered in the same region and with same detail + + // 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 + // We avoid cases where the adjPosition is in player chunk while the position is + // not // to always have a wall underwater - if(posToRender.contains(detailLevel, xAdj, zAdj) - && !isThisPositionGoingToBeRendered(LevelPosUtil.getChunkPos(detailLevel, xAdj), LevelPosUtil.getChunkPos(detailLevel,zAdj)) - && !(posNotInPlayerChunk && adjPosInPlayerChunk)) - { - for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++) - { - long data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex); - adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = false; - adjData.get(lodDirection)[verticalIndex] = data; - } - } - else - { - //Otherwise, we check if this position is - long data = lodDim.getSingleData(detailLevel, xAdj, zAdj); - - adjData.get(lodDirection)[0] = DataPointUtil.EMPTY_DATA; - - if ((isThisPositionGoingToBeRendered(LevelPosUtil.getChunkPos(detailLevel, xAdj), LevelPosUtil.getChunkPos(detailLevel,zAdj)) || - (posNotInPlayerChunk && adjPosInPlayerChunk)) - && !DataPointUtil.isVoid(data)) - { - adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = DataPointUtil.getAlpha(data) < 255; - } + if (posToRender.contains(detailLevel, xAdj, zAdj) + && !isThisPositionGoingToBeRendered(LevelPosUtil.getChunkPos(detailLevel, xAdj), + LevelPosUtil.getChunkPos(detailLevel, zAdj)) + && !(posNotInPlayerChunk && adjPosInPlayerChunk)) { + adjData[lodDirection.ordinal() - 2] = lodDim.getAllData(detailLevel, 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 - long data; - for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++) - { - - //we get the above block as adj UP - if (verticalIndex > 0) - adjData.get(LodDirection.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1); - else - adjData.get(LodDirection.UP)[0] = DataPointUtil.EMPTY_DATA; - - - //we get the below block as adj DOWN - if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1) - adjData.get(LodDirection.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1); - else - adjData.get(LodDirection.DOWN)[0] = DataPointUtil.EMPTY_DATA; - - //We extract the data to render - data = lodDim.getData(detailLevel, posX, posZ, verticalIndex); - - //If the data is not renderable (Void or non-existing) we stop since there is no data left in this position + 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; - - //We send the call to create the vertices - CubicLodTemplate.addLodToBuffer(currentBuffer, vboX, vboZ, data, adjData, - detailLevel, posX, posZ, vertexOptimizer, debugMode, adjShadeDisabled);//, cullingRangeX, cullingRangeZ); + + 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 - currentBuffer.end(); + // the thread executed successfully return regPos; } - + // Will be removed in a1.7 @Deprecated - private boolean isThisPositionGoingToBeRendered(int chunkX, int chunkZ){ + private boolean isThisPositionGoingToBeRendered(int chunkX, int chunkZ) { MovableGridList chunkGrid = ClientApi.renderer.vanillaRenderedChunks; Boolean isRendered = chunkGrid.get(chunkX, chunkZ); - + // skip any chunks that Minecraft is going to render - if (isRendered == null || !isRendered) return false; - + if (isRendered == null || !isRendered) + return false; + // check if the chunk is on the border if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER) return !LodUtil.isBorderChunk(ClientApi.renderer.vanillaRenderedChunks, chunkX, chunkZ); else return true; } - private final SpamReducedLogger ramLogger = new SpamReducedLogger(1); + public void dumpBufferMemoryUsage() { - if (!ramLogger.canMaybeLog()) return; + if (!ramLogger.canMaybeLog()) + return; ramLogger.info("Dumping Ram Usage for buffer usage..."); long bufferCount = 0; long fullBufferCount = 0; long totalUsage = 0; - long builderCount = 0; - long totalBuilderUsage = 0; - int maxLength = MAX_TRIANGLES_PER_BUFFER*(LodUtil.LOD_VERTEX_FORMAT.getByteSize()*3); + int maxLength = MAX_TRIANGLES_PER_BUFFER * (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3); if (buildableVbos == null) { ramLogger.info("Buildable VBOs are null!"); } else - for (LodVertexBuffer[] buffers : buildableVbos) { - if (buffers == null) continue; - LodVertexBuffer[] bs = buffers.clone(); - for (LodVertexBuffer b : bs) { - if (b == null) continue; - bufferCount++; - if (b.size == maxLength) { - fullBufferCount++; - } else if (b.size > maxLength) { - ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size), new UnitBytes(maxLength)); + for (RenderRegion buffers : buildableVbos) { + if (buffers == null) + continue; + LodVertexBuffer[] bs = buffers.debugGetBuffers().clone(); + for (LodVertexBuffer b : bs) { + if (b == null) + continue; + bufferCount++; + if (b.size == maxLength) { + fullBufferCount++; + } else if (b.size > maxLength) { + ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size), + new UnitBytes(maxLength)); + } + totalUsage += b.size; } - totalUsage += b.size; } - } - if (buildableBuffers == null) { - ramLogger.info("Buildable Buffers are null!"); - } else - for (LodBufferBuilder builder : buildableBuffers) { - if (builder == null) continue; - builderCount++; - totalBuilderUsage += builder.getMemUsage(); - } if (drawableVbos == null) { ramLogger.info("Drawable VBOs are null!"); } else - for (LodVertexBuffer[] buffers : drawableVbos) { - if (buffers == null) continue; - LodVertexBuffer[] bs = buffers.clone(); - for (LodVertexBuffer b : bs) { - if (b == null) continue; - bufferCount++; - if (b.size == maxLength) { - fullBufferCount++; - } else if (b.size > maxLength) { - ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size), new UnitBytes(maxLength)); + for (RenderRegion buffers : drawableVbos) { + if (buffers == null) + continue; + LodVertexBuffer[] bs = buffers.debugGetBuffers().clone(); + for (LodVertexBuffer b : bs) { + if (b == null) + continue; + bufferCount++; + if (b.size == maxLength) { + fullBufferCount++; + } else if (b.size > maxLength) { + ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size), + new UnitBytes(maxLength)); + } + totalUsage += b.size; } - totalUsage += b.size; } - } ramLogger.info("================================================"); - ramLogger.info("Buffers: [{}], Full-sized Buffers: [{}], Total: [{}]", - bufferCount, fullBufferCount, new UnitBytes(totalUsage)); - ramLogger.info("Builders: [{}], Total: [{}]", - builderCount, new UnitBytes(totalBuilderUsage)); + ramLogger.info("Buffers: [{}], Full-sized Buffers: [{}], Total: [{}]", bufferCount, fullBufferCount, + new UnitBytes(totalUsage)); ramLogger.info("================================================"); ramLogger.incLogTries(); } - - - - //===============================// + + // ===============================// // BufferBuilder related methods // - //===============================// - + // ===============================// + /** * Sets the buffers and Vbos to null, forcing them to be recreated
* and destroys any bound OpenGL objects.
@@ -656,17 +586,14 @@ public class LodBufferBuilderFactory * May have to wait for the bufferLock to open. */ public void destroyBuffers() { - MovableGridList toBeDeletedBuildableVbos; - MovableGridList toBeDeletedDrawableVbos; + MovableGridList toBeDeletedBuildableVbos; + MovableGridList toBeDeletedDrawableVbos; bufferLock.lock(); try { toBeDeletedBuildableVbos = buildableVbos; toBeDeletedDrawableVbos = drawableVbos; buildableVbos = null; drawableVbos = null; - // these don't contain any OpenGL objects, so - // they don't require any special clean-up - buildableBuffers = null; } finally { bufferLock.unlock(); } @@ -674,151 +601,103 @@ public class LodBufferBuilderFactory GLProxy.getInstance().recordOpenGlCall(() -> { // destroy the VBOs if they aren't already if (toBeDeletedBuildableVbos != null) { - toBeDeletedBuildableVbos.clear((vbos) -> { - for (LodVertexBuffer vbo : vbos) { - if (vbo == null) continue; - vbo.close(); - } - }); + toBeDeletedBuildableVbos.clear(RenderRegion::close); } if (toBeDeletedDrawableVbos != null) { - toBeDeletedDrawableVbos.clear((vbos) -> { - for (LodVertexBuffer vbo : vbos) { - if (vbo == null) continue; - vbo.close(); - } - }); + toBeDeletedDrawableVbos.clear(RenderRegion::close); } }); } - - /** Upload all buildableBuffers to the GPU. We should already be in the builder context */ - private void uploadBuffers(RegionPos p) - { + + /** + * Upload all buildableBuffers to the GPU. We should already be in the builder + * context + */ + private void uploadBuffers(LodQuadBuilder quadBuilder, RegionPos p) { GLProxy glProxy = GLProxy.getInstance(); GLProxyContext oldContext = glProxy.getGlContext(); glProxy.setGlContext(GLProxyContext.LOD_BUILDER); try { - // determine the upload method - GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod(); - - // determine the upload timeout - long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds(); // MB -> B = 1/1,000,000. MS -> NS = 1,000,000. So, MBPerMS = BPerNS. - long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS. - - // actually upload the buffers - LodBufferBuilder buffer = buildableBuffers.get(p.x, p.z); + // determine the upload method + GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod(); - ByteBuffer uploadBuffer = null; - //FIXME: The sonme Buffers aren't closed/end() and causing errors! - try { - LagSpikeCatcher b = new LagSpikeCatcher(); - uploadBuffer = buffer.getCleanedByteBuffer(); - b.end("getCleanedByteBuffer"); - } catch (IndexOutOfBoundsException e) { - // NOTE: Temp try/catch for above FIXME. - e.printStackTrace(); - } catch (RuntimeException e) { - ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage()); - e.printStackTrace(); - } - if (uploadBuffer == null) return; + // determine the upload timeout + long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds(); // MB -> B = + // 1/1,000,000. + // MS -> NS = + // 1,000,000. + // So, MBPerMS = + // BPerNS. + long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS. + long totalBytes = 0; + int vbosNeeded = quadBuilder.getCurrentNeededVertexBuffers(); - int maxLength = MAX_TRIANGLES_PER_BUFFER*(LodUtil.LOD_VERTEX_FORMAT.getByteSize()*3); + // Setup the VBO array LagSpikeCatcher vboSetup = new LagSpikeCatcher(); - LodVertexBuffer[] vbos = buildableVbos.get(p.x, p.z); - int requiredFullBuffers = Math.floorDiv(uploadBuffer.limit(),maxLength); - int additionalBuffer = Math.floorMod(uploadBuffer.limit(),maxLength); - if (vbos == null) { - vbos = new LodVertexBuffer[requiredFullBuffers+1]; - buildableVbos.set(p.x, p.z, vbos); - } else if (vbos.length != requiredFullBuffers+1) { - LodVertexBuffer[] newVbos = new LodVertexBuffer[requiredFullBuffers+1]; - if (vbos.length > requiredFullBuffers+1) { - for (int i=requiredFullBuffers+1; i iter = quadBuilder.makeVertexBuffers(); + int i = 0; + while (iter.hasNext()) { + ByteBuffer bb = iter.next(); LagSpikeCatcher vboU = new LagSpikeCatcher(); - vboUpload(p, i, subBuffer, uploadMethod); + vboUpload(p, i++, bb, uploadMethod); vboU.end("vboUpload"); - + // upload buffers over an extended period of time // to hopefully prevent stuttering. - remainingNS += subBuffer.limit()*BPerNS; - if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000/60, TimeUnit.MILLISECONDS)) { - if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; + totalBytes += bb.limit(); + remainingNS += bb.limit() * 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) {} + Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000)); + } catch (InterruptedException e) { + } remainingNS = 0; } } if (ENABLE_BUFFER_UPLOAD_LOGGING) - ClientApi.LOGGER.info("Uploaded {} sub buffers for {}", (requiredFullBuffers+1), p); + ClientApi.LOGGER.info("Uploaded {} sub buffers for {} with total of {}", i, p, + new UnitBytes(totalBytes)); } finally { glProxy.setGlContext(oldContext); } } - - /** Uploads the uploadBuffer so the GPU can use it. */ - private void vboUpload(RegionPos regPos, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod) - { - int maxLength = MAX_TRIANGLES_PER_BUFFER*(LodUtil.LOD_VERTEX_FORMAT.getByteSize()*3); - boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE; - LodVertexBuffer[] vbos = buildableVbos.get(regPos.x, regPos.z); - - if (vbos[iIndex] == null) { - vbos[iIndex] = new LodVertexBuffer(useBuffStorage); - } else if (vbos[iIndex].isBufferStorage != useBuffStorage) { - vbos[iIndex].close(); - vbos[iIndex] = new LodVertexBuffer(useBuffStorage); - } - LodVertexBuffer vbo = vbos[iIndex]; + /** Uploads the uploadBuffer so the GPU can use it. */ + private void vboUpload(RegionPos regPos, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod) { + int maxLength = MAX_TRIANGLES_PER_BUFFER * (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3); + boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE; + RenderRegion region = buildableVbos.get(regPos.x, regPos.z); + LodVertexBuffer vbo = region.getOrMakeVbo(iIndex, useBuffStorage); + // this is how many points will be rendered vbo.vertexCount = (uploadBuffer.limit() / LodUtil.LOD_VERTEX_FORMAT.getByteSize()); - + // If size is zero, just ignore it. - if (uploadBuffer.limit()==0) return; + if (uploadBuffer.limit() == 0) + return; LagSpikeCatcher bindBuff = new LagSpikeCatcher(); bindBuff.end("glBindBuffer vbo.id"); - + try { // if possible use the faster buffer storage route - if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE) - { + if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE) { GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); long size = vbo.size; - if (size < uploadBuffer.limit() || - size > uploadBuffer.limit()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER) - { - int newSize = (int)(uploadBuffer.limit()*BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxLength) newSize = maxLength; + if (size < uploadBuffer.limit() + || size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { + int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxLength) + newSize = maxLength; LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher(); GL32.glDeleteBuffers(vbo.id); vbo.id = GL32.glGenBuffers(); @@ -828,13 +707,11 @@ public class LodBufferBuilderFactory GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT); vbo.size = newSize; buffResize.end("glBufferStorage BuffStorage resize"); - } + } LagSpikeCatcher buffSubData = new LagSpikeCatcher(); GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer); buffSubData.end("glBufferSubData BuffStorage"); - } - else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) - { + } else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) { GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); // no stuttering but high GPU usage // stores everything in system memory instead of GPU memory @@ -842,11 +719,11 @@ public class LodBufferBuilderFactory // Unless the user is running integrated graphics, // in that case this will actually work better than SUB_DATA. long size = vbo.size; - if (size < uploadBuffer.limit() || - size > uploadBuffer.limit()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER) - { - int newSize = (int) (uploadBuffer.limit()*BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxLength) newSize = maxLength; + if (size < uploadBuffer.limit() + || size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { + int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxLength) + newSize = maxLength; LagSpikeCatcher buffResize = new LagSpikeCatcher(); GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); vbo.size = newSize; @@ -863,12 +740,9 @@ public class LodBufferBuilderFactory LagSpikeCatcher buffUnmap = new LagSpikeCatcher(); GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER); buffUnmap.end("glUnmapBuffer"); - - + buffWrite.end("WriteData BuffMapping"); - } - else if (uploadMethod == GpuUploadMethod.DATA) - { + } else if (uploadMethod == GpuUploadMethod.DATA) { GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); // TODO: Check this nonsense comment! // hybrid bufferData // @@ -878,19 +752,17 @@ public class LodBufferBuilderFactory GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW); vbo.size = uploadBuffer.limit(); buffData.end("glBufferData Data"); - } - else - { + } else { GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); // TODO: Check this nonsense comment! // hybrid subData/bufferData // // less stutter, low GPU usage long size = vbo.size; - if (size < uploadBuffer.limit() || - size > uploadBuffer.limit()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER) - { - int newSize = (int)(uploadBuffer.limit()*BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxLength) newSize = maxLength; + if (size < uploadBuffer.limit() + || size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { + int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxLength) + newSize = maxLength; LagSpikeCatcher buffResize = new LagSpikeCatcher(); GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); vbo.size = newSize; @@ -900,28 +772,25 @@ public class LodBufferBuilderFactory GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer); buffSubData.end("glBufferSubData SubData"); } - } - catch (Exception e) - { + } catch (Exception e) { ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName()); e.printStackTrace(); } - }//vboUpload - + }// vboUpload + private boolean swapBuffers() { bufferLock.lock(); if (ENABLE_BUFFER_SWAP_LOGGING) ClientApi.LOGGER.debug("Lod Swap Buffers"); { boolean shouldRegenBuff = true; - try - { - MovableGridList tmpVbo = drawableVbos; + try { + MovableGridList tmpVbo = drawableVbos; drawableVbos = buildableVbos; buildableVbos = tmpVbo; - //ClientApi.LOGGER.info("Lod Swapped Buffers: "+drawableVbos.toDetailString()); - + // ClientApi.LOGGER.info("Lod Swapped Buffers: "+drawableVbos.toDetailString()); + int tempX = drawableCenterBlockX; int tempY = drawableCenterBlockY; int tempZ = drawableCenterBlockZ; @@ -934,37 +803,32 @@ public class LodBufferBuilderFactory // the vbos have been swapped switchVbos = false; - //FIXME: Race condition on the allBuffersRequireReset boolean - shouldRegenBuff = frontBufferRequireReset || allBuffersRequireReset; + // FIXME: Race condition on the allBuffersRequireReset boolean + shouldRegenBuff = frontBufferRequireReset || allBuffersRequireReset; frontBufferRequireReset = allBuffersRequireReset; allBuffersRequireReset = false; hideFrontBuffer = hideBackBuffer; hideBackBuffer = false; - } - catch (Exception e) - { + } catch (Exception e) { // this shouldn't normally happen, but just in case it sill prevent deadlock ClientApi.LOGGER.error("swapBuffers ran into trouble: " + e.getMessage(), e); - } - finally - { + } finally { bufferLock.unlock(); } return shouldRegenBuff; } } - + /** Get the newly created VBOs */ - public MovableGridList getFrontBuffers() - { + public MovableGridList getFrontBuffers() { return shouldDrawFrontBuffer() ? drawableVbos : null; } - public int getFrontBuffersCenterX() - { + + public int getFrontBuffersCenterX() { return drawableCenterBlockX; } - public int getFrontBuffersCenterZ() - { + + public int getFrontBuffersCenterZ() { return drawableCenterBlockZ; } @@ -973,6 +837,7 @@ public class LodBufferBuilderFactory hideBackBuffer = true; hideFrontBuffer = true; } + public boolean shouldDrawFrontBuffer() { return !hideFrontBuffer; } diff --git a/src/main/java/com/seibel/lod/core/enums/LodDirection.java b/src/main/java/com/seibel/lod/core/enums/LodDirection.java index 2467ed14b..640d0ff6b 100644 --- a/src/main/java/com/seibel/lod/core/enums/LodDirection.java +++ b/src/main/java/com/seibel/lod/core/enums/LodDirection.java @@ -23,7 +23,20 @@ public enum LodDirection SOUTH(3, 2, 0, "south", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Z, new Vec3i(0, 0, 1)), WEST(4, 5, 1, "west", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.X, new Vec3i(-1, 0, 0)), EAST(5, 4, 3, "east", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.X, new Vec3i(1, 0, 0)); - + public static final LodDirection[] DIRECTIONS = new LodDirection[] { + LodDirection.UP, + LodDirection.DOWN, + LodDirection.WEST, + LodDirection.EAST, + LodDirection.NORTH, + LodDirection.SOUTH }; + + /** North, South, East, West */ + public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] { + LodDirection.EAST, + LodDirection.WEST, + LodDirection.SOUTH, + LodDirection.NORTH }; // private final int data3d; // private final int oppositeIndex; // private final int data2d; diff --git a/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java deleted file mode 100644 index 79d135466..000000000 --- a/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java +++ /dev/null @@ -1,591 +0,0 @@ -/* - * This file is part of the Distant Horizon mod (formerly the LOD Mod), - * licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.seibel.lod.core.objects; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import com.seibel.lod.core.enums.LodDirection; -import com.seibel.lod.core.enums.rendering.DebugMode; -import com.seibel.lod.core.objects.math.Vec3i; -import com.seibel.lod.core.util.ColorUtil; -import com.seibel.lod.core.util.DataPointUtil; -import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.core.util.SingletonHandler; -import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; -import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; - -/** - * This class handles all the vertex optimization that's needed for a column of lods. W - * @author Leonardo Amato - * @version 10-2-2021 - */ -public class VertexOptimizer -{ - private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); - private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); - - public static final int ADJACENT_HEIGHT_INDEX = 0; - public static final int ADJACENT_DEPTH_INDEX = 1; - - public static final int X = 0; - public static final int Y = 1; - public static final int Z = 2; - - public static final int MIN = 0; - public static final int MAX = 1; - - public static final int VOID_FACE = 0; - - /** The six cardinal directions */ - public static final LodDirection[] DIRECTIONS = new LodDirection[] { - LodDirection.UP, - LodDirection.DOWN, - LodDirection.WEST, - LodDirection.EAST, - LodDirection.NORTH, - LodDirection.SOUTH }; - - /** North, South, East, West */ - public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] { - LodDirection.EAST, - LodDirection.WEST, - LodDirection.SOUTH, - LodDirection.NORTH }; - - /** All the faces and vertices of a cube. This is used to extract the vertex from the column */ - @SuppressWarnings("serial") - public static final Map DIRECTION_VERTEX_MAP = new HashMap() - {{ - put(LodDirection.UP, new int[][] { - { 0, 1, 0 }, // 0 - { 0, 1, 1 }, // 1 - { 1, 1, 1 }, // 2 - - { 0, 1, 0 }, // 0 - { 1, 1, 1 }, // 2 - { 1, 1, 0 } // 3 - }); - put(LodDirection.DOWN, new int[][] { - { 1, 0, 0 }, // 0 - { 1, 0, 1 }, // 1 - { 0, 0, 1 }, // 2 - - { 1, 0, 0 }, // 0 - { 0, 0, 1 }, // 2 - { 0, 0, 0 } // 3 - }); - put(LodDirection.EAST, new int[][] { - { 1, 1, 0 }, // 0 - { 1, 1, 1 }, // 1 - { 1, 0, 1 }, // 2 - - { 1, 1, 0 }, // 0 - { 1, 0, 1 }, // 2 - { 1, 0, 0 } }); // 3 - put(LodDirection.WEST, new int[][] { - { 0, 0, 0 }, // 0 - { 0, 0, 1 }, // 1 - { 0, 1, 1 }, // 2 - - { 0, 0, 0 }, // 0 - { 0, 1, 1 }, // 2 - { 0, 1, 0 } // 3 - }); - put(LodDirection.SOUTH, new int[][] { - { 1, 0, 1 }, // 0 - { 1, 1, 1 }, // 1 - { 0, 1, 1 }, // 2 - - { 1, 0, 1 }, // 0 - { 0, 1, 1 }, // 2 - { 0, 0, 1 } // 3 - }); - put(LodDirection.NORTH, new int[][] { - { 0, 0, 0 }, // 0 - { 0, 1, 0 }, // 1 - { 1, 1, 0 }, // 2 - - { 0, 0, 0 }, // 0 - { 1, 1, 0 }, // 2 - { 1, 0, 0 } // 3 - }); - }}; - - - /** - * This indicates which position is invariable in the DIRECTION_VERTEX_MAP. - * Is used to extract the vertex - */ - @SuppressWarnings("serial") - public static final Map FACE_DIRECTION = new HashMap() - {{ - put(LodDirection.UP, new int[] { Y, MAX }); - put(LodDirection.DOWN, new int[] { Y, MIN }); - put(LodDirection.EAST, new int[] { X, MAX }); - put(LodDirection.WEST, new int[] { X, MIN }); - put(LodDirection.SOUTH, new int[] { Z, MAX }); - put(LodDirection.NORTH, new int[] { Z, MIN }); - }}; - - - /** - * This is a map from Direction to the relative normal vector - * we are using this since I'm not sure if the getNormal create new object at every call - */ - // FIXME: No. It doesn't. Just remove this. - @SuppressWarnings("serial") - public static final Map DIRECTION_NORMAL_MAP = new HashMap() - {{ - put(LodDirection.UP, LodDirection.UP.getNormal()); - put(LodDirection.DOWN, LodDirection.DOWN.getNormal()); - put(LodDirection.EAST, LodDirection.EAST.getNormal()); - put(LodDirection.WEST, LodDirection.WEST.getNormal()); - put(LodDirection.SOUTH, LodDirection.SOUTH.getNormal()); - put(LodDirection.NORTH, LodDirection.NORTH.getNormal()); - }}; - - /** We use this index for all array that are going to */ - @SuppressWarnings("serial") - public static final Map DIRECTION_INDEX = new HashMap() - {{ - put(LodDirection.UP, 0); - put(LodDirection.DOWN, 1); - put(LodDirection.EAST, 2); - put(LodDirection.WEST, 3); - put(LodDirection.SOUTH, 4); - put(LodDirection.NORTH, 5); - }}; - - @SuppressWarnings("serial") - public static final Map ADJ_DIRECTION_INDEX = new HashMap() - {{ - put(LodDirection.EAST, 0); - put(LodDirection.WEST, 1); - put(LodDirection.SOUTH, 2); - put(LodDirection.NORTH, 3); - }}; - /** holds the box's x, y, z offset */ - public final int[] boxOffset; - /** holds the box's x, y, z width */ - public final int[] boxWidth; - - /** Holds each direction's color */ - public final int[] colorMap; - /** The original color (before shading) of this box */ - public int color; - /** - * - */ - public final Map adjHeight; - public final Map adjDepth; - public final Map skyLights; - public byte blockLight; - - boolean skipTop; - boolean skipBot; - - - - /** creates an empty box */ - @SuppressWarnings("serial") - public VertexOptimizer() - { - boxOffset = new int[3]; - boxWidth = new int[3]; - - colorMap = new int[6]; - skyLights = new HashMap() - {{ - put(LodDirection.UP, new byte[1]); - put(LodDirection.DOWN, new byte[1]); - put(LodDirection.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - }}; - adjHeight = new HashMap() - {{ - put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - }}; - adjDepth = new HashMap() - {{ - put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); - }}; - } - - /** Set the light of the columns */ - public void setLights(int skyLight, int blockLight) - { - this.blockLight = (byte) blockLight; - skyLights.get(LodDirection.UP)[0] = (byte) skyLight; - } - - /** - * Set the color of the columns - * @param color color to add - * @param adjShadeDisabled this array indicates which face does not need shading - */ - public void setColor(int color, boolean[] adjShadeDisabled) - { - this.color = color; - for (LodDirection lodDirection : DIRECTIONS) - { - if (!adjShadeDisabled[DIRECTION_INDEX.get(lodDirection)]) - colorMap[DIRECTION_INDEX.get(lodDirection)] = ColorUtil.applyShade(color, MC.getShade(lodDirection)); - else - colorMap[DIRECTION_INDEX.get(lodDirection)] = color; - } - } - - /** - * @param lodDirection of the face of which we want to get the color - * @return color of the face - */ - public int getColor(LodDirection lodDirection) - { - if (CONFIG.client().advanced().debugging().getDebugMode() != DebugMode.SHOW_DETAIL && - CONFIG.client().advanced().debugging().getDebugMode() != DebugMode.SHOW_WIREFRAME) - return colorMap[DIRECTION_INDEX.get(lodDirection)]; - else - return ColorUtil.applyShade(color, MC.getShade(lodDirection)); - } - - /** - */ - public byte getSkyLight(LodDirection lodDirection, int verticalIndex) - { - if(lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN) - return skyLights.get(lodDirection)[0]; - else - return skyLights.get(lodDirection)[verticalIndex]; - } - - /** - */ - public int getBlockLight() - { - return blockLight; - } - /** clears this box, resetting everything to default values */ - public void reset() - { - Arrays.fill(boxWidth, 0); - Arrays.fill(boxOffset, 0); - Arrays.fill(colorMap, 0); - blockLight = 0; - for (LodDirection lodDirection : ADJ_DIRECTIONS) - { - for (int i = 0; i < adjHeight.get(lodDirection).length; i++) - { - adjHeight.get(lodDirection)[i] = VOID_FACE; - adjDepth.get(lodDirection)[i] = VOID_FACE; - skyLights.get(lodDirection)[i] = 0; - } - } - } - - /** - * This method create all the shared face culling based on the adjacent data - * @param adjData data adjacent to the column we are going to render - */ - public void setAdjData(Map adjData) - { - int height; - int depth; - int minY = getMinY(); - int maxY = getMaxY(); - long singleAdjDataPoint; - - // TODO transparency uncomment final condition to see ocean floor - //Up direction case - singleAdjDataPoint = adjData.get(LodDirection.UP)[0]; - skipTop = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getDepth(singleAdjDataPoint) == maxY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255; - //Down direction case - singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0]; - skipBot = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getHeight(singleAdjDataPoint) == minY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255; - if(DataPointUtil.doesItExist(singleAdjDataPoint)) - skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSky(singleAdjDataPoint); - else - skyLights.get(LodDirection.DOWN)[0] = skyLights.get(LodDirection.UP)[0]; - //other sided - //TODO clean some similar cases - for (LodDirection lodDirection : ADJ_DIRECTIONS) - { - long[] dataPoint = adjData.get(lodDirection); - if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0])) - { - adjHeight.get(lodDirection)[0] = maxY; - adjDepth.get(lodDirection)[0] = minY; - adjHeight.get(lodDirection)[1] = VOID_FACE; - adjDepth.get(lodDirection)[1] = VOID_FACE; - skyLights.get(lodDirection)[0] = 15; //in void set full skylight - continue; - } - - int i; - int faceToDraw = 0; - boolean firstFace = true; - boolean toFinish = false; - int toFinishIndex = 0; - boolean allAbove = true; - // TODO transparency ocean floor fix - //boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255; - for (i = 0; i < dataPoint.length; i++) - { - singleAdjDataPoint = dataPoint[i]; - if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint)) - break; - - // TODO transparency ocean floor fix - //if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255) - // continue; - - height = DataPointUtil.getHeight(singleAdjDataPoint); - depth = DataPointUtil.getDepth(singleAdjDataPoint); - - if (depth < maxY) - { - allAbove = false; - if (height < minY) - { - // the adj data is lower than the current data - - if (firstFace) - { - adjHeight.get(lodDirection)[0] = getMaxY(); - adjDepth.get(lodDirection)[0] = getMinY(); - skyLights.get(lodDirection)[0] = DataPointUtil.getLightSky(singleAdjDataPoint); //skyLights.get(Direction.UP)[0]; - } - else - { - adjDepth.get(lodDirection)[faceToDraw] = getMinY(); - skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSky(singleAdjDataPoint); - } - faceToDraw++; - toFinish = false; - - // break since all the other data will be lower - break; - } - else if (depth <= minY) - { - if (height >= maxY) - { - // the adj data is inside the current data - // don't draw the face - adjHeight.get(lodDirection)[0] = VOID_FACE; - adjDepth.get(lodDirection)[0] = VOID_FACE; - } - else // height < maxY - { - // the adj data intersects the lower part of the current data - // if this is the only face, use the maxY and break, - // if there was another face we finish the last one and break - if (firstFace) - { - adjHeight.get(lodDirection)[0] = getMaxY(); - adjDepth.get(lodDirection)[0] = height; - skyLights.get(lodDirection)[0] = DataPointUtil.getLightSky(singleAdjDataPoint); //skyLights.get(Direction.UP)[0]; - } - else - { - adjDepth.get(lodDirection)[faceToDraw] = height; - skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSky(singleAdjDataPoint); - } - toFinish = false; - faceToDraw++; - } - break; - } - else if (height >= maxY)// && depth > minY - { - // the adj data intersects the higher part of the current data - // we start the creation of a new face - adjHeight.get(lodDirection)[faceToDraw] = depth; - //skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint); - firstFace = false; - toFinish = true; - toFinishIndex = i + 1; - } - else - { - // if (depth > minY && height < maxY) - - // the adj data is contained in the current data - if (firstFace) - { - adjHeight.get(lodDirection)[0] = getMaxY(); - } - - adjDepth.get(lodDirection)[faceToDraw] = height; - skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSky(singleAdjDataPoint); - faceToDraw++; - adjHeight.get(lodDirection)[faceToDraw] = depth; - firstFace = false; - toFinish = true; - toFinishIndex = i + 1; - } - } - } - - if (allAbove) - { - adjHeight.get(lodDirection)[0] = getMaxY(); - adjDepth.get(lodDirection)[0] = getMinY(); - skyLights.get(lodDirection)[0] = skyLights.get(LodDirection.UP)[0]; - faceToDraw++; - } - else if (toFinish) - { - adjDepth.get(lodDirection)[faceToDraw] = minY; - if(toFinishIndex < dataPoint.length) - { - singleAdjDataPoint = dataPoint[toFinishIndex]; - if (DataPointUtil.doesItExist(singleAdjDataPoint)) - skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSky(singleAdjDataPoint); - else - skyLights.get(lodDirection)[faceToDraw] = skyLights.get(LodDirection.UP)[0]; - } - faceToDraw++; - } - - adjHeight.get(lodDirection)[faceToDraw] = VOID_FACE; - adjDepth.get(lodDirection)[faceToDraw] = VOID_FACE; - } - } - - /** We use this method to set the various width of the column */ - public void setWidth(int xWidth, int yWidth, int zWidth) - { - boxWidth[X] = xWidth; - boxWidth[Y] = yWidth; - boxWidth[Z] = zWidth; - } - - /** We use this method to set the various offset of the column */ - public void setOffset(int xOffset, int yOffset, int zOffset) - { - boxOffset[X] = xOffset; - boxOffset[Y] = yOffset; - boxOffset[Z] = zOffset; - } - - /** returns true if the given direction should be rendered. */ - public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex) - { - if (lodDirection == LodDirection.UP) - return adjIndex == 0 && !skipTop; - if (lodDirection == LodDirection.DOWN) - return adjIndex == 0 && !skipBot; - - return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE); - } - - - /** - * @param lodDirection direction of the face we want to render - * @param vertexIndex index of the vertex of the face (0-1-2-3) - * @return position x of the relative vertex - */ - public int getX(LodDirection lodDirection, int vertexIndex) - { - return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][X]; - } - - /** - * @param lodDirection direction of the face we want to render - * @param vertexIndex index of the vertex of the face (0-1-2-3) - * @return position y of the relative vertex - */ - public int getY(LodDirection lodDirection, int vertexIndex) - { - return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y]; - } - - /** - * @param lodDirection direction of the face we want to render - * @param vertexIndex index of the vertex of the face (0-1-2-3) - * @param adjIndex, index of the n-th culled face of this direction - * @return position x of the relative vertex - */ - public int getY(LodDirection lodDirection, int vertexIndex, int adjIndex) - { - if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.UP) - return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y]; - else - { - // this could probably be cleaned up more, - // but it still works - if (1 - DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX) - return adjHeight.get(lodDirection)[adjIndex]; - else - return adjDepth.get(lodDirection)[adjIndex]; - } - } - - /** - * @param lodDirection direction of the face we want to render - * @param vertexIndex index of the vertex of the face (0-1-2-3) - * @return position z of the relative vertex - */ - public int getZ(LodDirection lodDirection, int vertexIndex) - { - return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Z]; - } - - public int getMinX() - { - return boxOffset[X]; - } - - public int getMaxX() - { - return boxOffset[X] + boxWidth[X]; - } - - public int getMinY() - { - return boxOffset[Y]; - } - - public int getMaxY() - { - return boxOffset[Y] + boxWidth[Y]; - } - - public int getMinZ() - { - return boxOffset[Z]; - } - - public int getMaxZ() - { - return boxOffset[Z] + boxWidth[Z]; - } - -} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java index a64ec592e..4a7d64fbf 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java @@ -68,6 +68,14 @@ public interface LevelContainer * @return the data in long array format */ long getData(int posX, int posZ, int index); + + /** + * With this you can get data from the level container + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @return the data in long array format + */ + long[] getAllData(int posX, int posZ); /** * With this you can get data from the level container 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 963c7b416..5f655c680 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 @@ -550,6 +550,25 @@ public class LodDimension return region.getData(detailLevel, posX, posZ, verticalIndex); } + /** + * Get the data point at the given X and Z coordinates + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ + public long[] getAllData(byte detailLevel, int posX, int posZ) + { + if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); + + LodRegion region = getRegion(detailLevel, posX, posZ); + if (region == null) + return null; + + return region.getAllData(detailLevel, posX, posZ); + } + /** * Get the data point at the given X and Z coordinates 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 7b034e17c..59e189c99 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 @@ -190,6 +190,18 @@ public class LodRegion { posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); return dataContainer[detailLevel].getData(posX, posZ, verticalIndex); } + + /** + * Get the dataPoint at the given relative position. + * + * @return the data at the relative pos and detail level, 0 if the data doesn't + * exist. + */ + public long[] getAllData(byte detailLevel, int posX, int posZ) { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + return dataContainer[detailLevel].getAllData(posX, posZ); + } /** * Get the dataPoint at the given relative position. diff --git a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java index 5a066aff5..c140e9c24 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java @@ -26,8 +26,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; -import com.seibel.lod.core.dataFormat.*; -import com.seibel.lod.core.enums.config.DistanceGenerationMode; import com.seibel.lod.core.util.*; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; @@ -188,6 +186,15 @@ public class VerticalLevelContainer implements LevelContainer return dataContainer[posX * size * verticalSize + posZ * verticalSize]; } + @Override + public long[] getAllData(int posX, int posZ) + { + long[] result = new long[verticalSize]; + int index = posX * size * verticalSize + posZ * verticalSize; + System.arraycopy(dataContainer, index, result, 0, verticalSize); + return result; + } + @Override public int getVerticalSize() { diff --git a/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java b/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java index 88588131e..ce2b3c372 100644 --- a/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java +++ b/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java @@ -397,6 +397,7 @@ public class Mat4f /** * TODO: what kind of translation is this? * and how is this different from "multiplyTranslationMatrix"? + * Answer: This is faster and direct (but only if this is pure translation matrix without rotate) */ public void translate(Vec3f vec) { diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java b/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java index 5cb028e95..6da5ad8c4 100644 --- a/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java +++ b/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java @@ -30,14 +30,14 @@ import com.google.common.collect.ImmutableList; */ public class DefaultLodVertexFormats { - public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 3, false); + public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(3, LodVertexFormatElement.DataType.USHORT, 3, false); public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4, false); public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2, false); public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2, false); public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3, false); public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1, true); - public static final LodVertexFormatElement ELEMENT_BLOCK_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1, false); + public static final LodVertexFormatElement ELEMENT_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1, false); public static final LodVertexFormat POSITION = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).build()); @@ -47,5 +47,7 @@ public class DefaultLodVertexFormats public static final LodVertexFormat POSITION_COLOR_TEX = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).build()); public static final LodVertexFormat POSITION_COLOR_TEX_LIGHTMAP = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).add(ELEMENT_LIGHT_MAP_UV).build()); - public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_BLOCK_LIGHT).add(ELEMENT_BLOCK_LIGHT).add(ELEMENT_PADDING).add(ELEMENT_PADDING).build()); + public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT = new LodVertexFormat(ImmutableList.builder() + .add(ELEMENT_POSITION).add(ELEMENT_PADDING).add(ELEMENT_LIGHT) + .add(ELEMENT_COLOR).build()); } diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodBox.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodBox.java new file mode 100644 index 000000000..e12510e2a --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodBox.java @@ -0,0 +1,161 @@ +package com.seibel.lod.core.objects.opengl; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; + +public class LodBox { + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + + public static void addBoxQuadsToBuilder(LodQuadBuilder builder, short xSize, short ySize, short zSize, short x, + short y, short z, int color, byte skyLight, byte blockLight, long topData, long botData, long[][] adjData) { + short maxX = (short) (x + xSize); + short maxY = (short) (y + ySize); + short maxZ = (short) (z + zSize); + byte skyLightTop = skyLight; + byte skyLightBot = DataPointUtil.doesItExist(botData) ? DataPointUtil.getLightSky(botData) : 0; + + // Up direction case + boolean skipTop = DataPointUtil.doesItExist(topData) && DataPointUtil.getDepth(topData) == maxY;// && + // DataPointUtil.getAlpha(singleAdjDataPoint) + // == 255; + boolean skipBot = DataPointUtil.doesItExist(botData) && DataPointUtil.getHeight(botData) == y;// && + // DataPointUtil.getAlpha(singleAdjDataPoint) + // == 255; + + if (!skipTop) + builder.addQuadUp(x, maxY, z, xSize, zSize, color, skyLightTop, blockLight); + if (!skipBot) + builder.addQuadDown(x, y, z, xSize, zSize, color, skyLightBot, blockLight); + makeAdjQuads(builder, adjData[LodDirection.NORTH.ordinal() - 2], LodDirection.NORTH, x, y, z, xSize, ySize, + color, skyLightTop, blockLight); + makeAdjQuads(builder, adjData[LodDirection.SOUTH.ordinal() - 2], LodDirection.SOUTH, x, y, maxZ, xSize, ySize, + color, skyLightTop, blockLight); + makeAdjQuads(builder, adjData[LodDirection.WEST.ordinal() - 2], LodDirection.WEST, x, y, z, zSize, ySize, color, + skyLightTop, blockLight); + makeAdjQuads(builder, adjData[LodDirection.EAST.ordinal() - 2], LodDirection.EAST, maxX, y, z, zSize, ySize, + color, skyLightTop, blockLight); + } + + private static void makeAdjQuads(LodQuadBuilder builder, long[] adjData, LodDirection direction, short x, short y, + short z, short w0, short wy, int color, byte upSkyLight, byte blockLight) { + color = ColorUtil.applyShade(color, MC.getShade(direction)); + long[] dataPoint = adjData; + if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0])) { + builder.addQuadAdj(direction, x, y, z, w0, wy, color, (byte) 15, blockLight); + return; + } + + int i; + boolean firstFace = true; + boolean allAbove = true; + short nextStartingHeight = -1; + byte nextSkyLight = upSkyLight; + + // TODO transparency ocean floor fix + // boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255; + for (i = 0; i < dataPoint.length && DataPointUtil.doesItExist(adjData[i]) + && !DataPointUtil.isVoid(adjData[i]); i++) { + long adjPoint = adjData[i]; + + // TODO transparency ocean floor fix + // if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255) + // continue; + + short height = DataPointUtil.getHeight(adjPoint); + short depth = DataPointUtil.getDepth(adjPoint); + + // If the depth of said block is higher then our max Y, continue + // Basically: y < maxY <= _____ height + // _______&&: y < maxY <= depth + if (y + wy <= depth) + continue; + // Now: depth < maxY + allAbove = false; + + if (height < y) { + // Basically: _____ height < y < maxY + // _______&&: depth ______ < y < maxY + if (firstFace) { + builder.addQuadAdj(direction, x, y, z, w0, wy, color, DataPointUtil.getLightSky(adjPoint), + blockLight); + } else { + // Now: depth < height < y < previousDepth < maxY + if (nextStartingHeight == -1) + throw new RuntimeException("Loop error"); + builder.addQuadAdj(direction, x, y, z, w0, (short) (nextStartingHeight - y), color, + DataPointUtil.getLightSky(adjPoint), blockLight); + nextStartingHeight = -1; + } + break; + } + + if (depth <= y) { // AND y <= height + if (y + wy <= height) { + // Basically: ________ y < maxY <= height + // _______&&: depth <= y < maxY + // The face is inside adj face completely. Don't draw. + break; + } + // Otherwise: ________ y <= Height < maxY + // _______&&: depth <= y _________ < maxY + // the adj data intersects the lower part of the current data + // if this is the only face, use the maxY and break, + // if there was another face we finish the last one and break + if (firstFace) { + builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color, + DataPointUtil.getLightSky(adjPoint), blockLight); + } else { + // Now: depth <= y <= height < previousDepth < maxY + if (nextStartingHeight == -1) + throw new RuntimeException("Loop error"); + builder.addQuadAdj(direction, x, height, z, w0, (short) (nextStartingHeight - height), color, + DataPointUtil.getLightSky(adjPoint), blockLight); + nextStartingHeight = -1; + } + break; + } + + // In here always true: y < depth < maxY + // _________________&&: y < _____ (height and maxY) + + if (y + wy <= height) { + // Basically: y _______ < maxY <= height + // _______&&: y < depth < maxY + // the adj data intersects the higher part of the current data + // we start the creation of a new face + } else { + // Otherwise: y < _____ height < maxY + // _______&&: y < depth ______ < maxY + if (firstFace) { + builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color, + DataPointUtil.getLightSky(adjPoint), blockLight); + } else { + // Now: y < depth < height < previousDepth < maxY + if (nextStartingHeight == -1) + throw new RuntimeException("Loop error"); + builder.addQuadAdj(direction, x, height, z, w0, (short) (nextStartingHeight - height), color, + DataPointUtil.getLightSky(adjPoint), blockLight); + nextStartingHeight = -1; + } + } + // set next top as current depth + nextStartingHeight = depth; + firstFace = false; + nextSkyLight = upSkyLight; + if (i + 1 < adjData.length && DataPointUtil.doesItExist(adjData[i + 1])) + nextSkyLight = DataPointUtil.getLightSky(adjData[i + 1]); + } + + if (allAbove) { + builder.addQuadAdj(direction, x, y, z, w0, wy, color, upSkyLight, blockLight); + } else if (nextStartingHeight != -1) { + // We need to finish the last quad. + builder.addQuadAdj(direction, x, y, z, w0, (short) (nextStartingHeight - y), color, nextSkyLight, + blockLight); + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java deleted file mode 100644 index 86c75d7a6..000000000 --- a/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * This file is part of the Distant Horizon mod (formerly the LOD Mod), - * licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.seibel.lod.core.objects.opengl; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import com.google.common.collect.ImmutableList; - -/** - * A (almost) exact copy of Minecraft's - * BufferBuilder object.
- * Which allows for creating and filling - * OpenGL buffers. - * - * @author James Seibel - * @version 12-9-2021 - */ -public class LodBufferBuilder -{ - public ByteBuffer buffer; - - private int nextElementByte = 0; - private int vertices; - private LodVertexFormatElement currentElement; - private int elementIndex; - private LodVertexFormat format; - private boolean building; - - public LodBufferBuilder(int bufferSizeInBytes) - { - this.buffer = allocateByteBuffer(bufferSizeInBytes); - } - - public int getMemUsage() {return buffer.capacity();} - - /** originally from MC's GLAllocation class */ - private ByteBuffer allocateByteBuffer(int bufferSizeInBytes) - { - return ByteBuffer.allocateDirect(bufferSizeInBytes).order(ByteOrder.nativeOrder()); - } - - - /** make sure the buffer doesn't overflow when inserting new elements */ - private void ensureVertexCapacity() - { - this.ensureCapacity(this.format.getByteSize()); - } - private void ensureCapacity(int vertexSizeInBytes) - { - if (this.nextElementByte + vertexSizeInBytes > this.buffer.capacity()) - { - int i = this.buffer.capacity(); - int j = roundUp((int) ((i + vertexSizeInBytes)*2)); - //LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", i, j); - ByteBuffer bytebuffer = allocateByteBuffer(j); - this.buffer.position(0); - bytebuffer.put(this.buffer); - bytebuffer.rewind(); - this.buffer = bytebuffer; - } - } - private void packBuffer() { - int cap = this.buffer.capacity(); - int filled = this.format.getByteSize() * this.vertices; - this.buffer.position(0); - this.buffer.limit(filled); - if (cap - filled > 4096) { - ByteBuffer bytebuffer = allocateByteBuffer(filled); - bytebuffer.put(this.buffer); - bytebuffer.rewind(); - this.buffer = bytebuffer; - } - - } - - private static int roundUp(int vertexSizeInBytes) - { - int i = 4096; // 4 KB (1 page) - if (vertexSizeInBytes == 0) - { - return i; - } - else - { - if (vertexSizeInBytes < 0) - { - i *= -1; - } - - int j = vertexSizeInBytes % i; - return j == 0 ? vertexSizeInBytes : vertexSizeInBytes + i - j; - } - } - - private void switchFormat(LodVertexFormat newFormat) - { - format = newFormat; - } - - - - - //========================================// - // methods for actually building a buffer // - //========================================// - - /** - * @param openGlLodVertexFormat GL11.GL_QUADS, GL11.GL_TRIANGLES, etc. - * @param LodVertexFormat - */ - public void begin(int openGlLodVertexFormat, LodVertexFormat LodVertexFormat) - { - if (this.building) - { - throw new IllegalStateException("Already building!"); - } - else - { - this.building = true; - this.switchFormat(LodVertexFormat); - this.currentElement = LodVertexFormat.getElements().get(0); - this.elementIndex = 0; - this.buffer.clear(); - this.vertices = 0; - } - } - - public void end() - { - if (!this.building) - { - return; - } else { - this.building = false; - this.currentElement = null; - this.elementIndex = 0; - packBuffer(); - } - } - - public void putByte(int index, byte newByte) - { - this.buffer.put(this.nextElementByte + index, newByte); - } - - public void putShort(int index, short newShort) - { - this.buffer.putShort(this.nextElementByte + index, newShort); - } - - public void putFloat(int index, float newFloat) - { - this.buffer.putFloat(this.nextElementByte + index, newFloat); - } - - public void endVertex() - { - if (this.elementIndex != 0) - { - throw new IllegalStateException("Not filled all elements of the vertex"); - } - else - { - ++this.vertices; - this.ensureVertexCapacity(); - } - } - - public void nextElement() - { - ImmutableList immutablelist = this.format.getElements(); - this.elementIndex = (this.elementIndex + 1) % immutablelist.size(); - this.nextElementByte += this.currentElement.getByteSize(); - this.currentElement = immutablelist.get(this.elementIndex); - if (currentElement.getIsPadding()) - { - this.nextElement(); - } - -// if (this.defaultColorSet && this.currentElement.getUsage() == LodVertexFormatElement.Usage.COLOR) -// { -// color(this.defaultR, this.defaultG, this.defaultB, this.defaultA); -// } - - } - - public LodBufferBuilder color(int red, int green, int blue, int alpha) - { - LodVertexFormatElement LodVertexFormatelement = this.currentElement(); - if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE) - { - throw new IllegalStateException("Color must be stored as a UBYTE"); - } - else - { - this.putByte(0, (byte) red); - this.putByte(1, (byte) green); - this.putByte(2, (byte) blue); - this.putByte(3, (byte) alpha); - this.nextElement(); - return this; - } - } - - public LodBufferBuilder minecraftLightValue(byte lightValue) - { - LodVertexFormatElement LodVertexFormatelement = this.currentElement(); - if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE) - { - throw new IllegalStateException("Light Color must be stored as a UBYTE"); - } - else - { - this.putByte(0, lightValue); - this.nextElement(); - return this; - } - } - - public LodBufferBuilder position(float x, float y, float z) - { - if (this.currentElement().getType() != LodVertexFormatElement.DataType.FLOAT) - { - throw new IllegalStateException("Position verticies must be stored as a FLOAT"); - } - else - { - this.putFloat(0, x); - this.putFloat(4, y); - this.putFloat(8, z); - this.nextElement(); - return this; - } - } - - public ByteBuffer getCleanedByteBuffer() - { - return this.buffer; - } - - public void reset() - { - this.nextElementByte = 0; - this.vertices = 0; - this.buffer.clear(); - } - - public LodVertexFormatElement currentElement() - { - if (this.currentElement == null) - { - throw new IllegalStateException("BufferBuilder not started"); - } - else - { - return this.currentElement; - } - } - - public boolean building() - { - return this.building; - } - - //==================// - // internal classes // - //==================// - - public static final class DrawState - { - private final LodVertexFormat format; - private final int vertexCount; - private final int mode; - - private DrawState(LodVertexFormat p_i225905_1_, int p_i225905_2_, int p_i225905_3_) - { - this.format = p_i225905_1_; - this.vertexCount = p_i225905_2_; - this.mode = p_i225905_3_; - } - - public LodVertexFormat format() - { - return this.format; - } - - public int vertexCount() - { - return this.vertexCount; - } - - public int mode() - { - return this.mode; - } - } - - public LodVertexFormat getLodVertexFormat() - { - return this.format; - } -} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodQuadBuilder.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodQuadBuilder.java new file mode 100644 index 000000000..f46054a71 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodQuadBuilder.java @@ -0,0 +1,228 @@ +package com.seibel.lod.core.objects.opengl; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Iterator; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.LodDirection.Axis; +import com.seibel.lod.core.util.ColorUtil; + +public class LodQuadBuilder { + static final int MAX_BUFFER_SIZE = (1024 * 1024 * 1); + static final int QUAD_BYTE_SIZE = (12 * 6); + static final int MAX_QUADS_PER_BUFFER = MAX_BUFFER_SIZE / QUAD_BYTE_SIZE; + + static class Quad { + final short x; + final short y; + final short z; + final short w0; + final short w1; + final int color; + final byte skylight; + final byte blocklight; + final LodDirection dir; + + Quad(short x, short y, short z, short w0, short w1, int color, byte skylight, byte blocklight, + LodDirection dir) { + this.x = x; + this.y = y; + this.z = z; + this.w0 = w0; + this.w1 = w1; + this.color = color; + this.skylight = skylight; + this.blocklight = blocklight; + this.dir = dir; + } + } + + final ArrayList quads; + + public LodQuadBuilder(int initialSize) { + quads = new ArrayList(initialSize); + } + + public void addQuadAdj(LodDirection dir, short x, short y, short z, short w0, short wy, int color, byte skylight, + byte blocklight) { + if (dir.ordinal() <= LodDirection.DOWN.ordinal()) + throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!"); + quads.add(new Quad(x, y, z, w0, wy, color, skylight, blocklight, dir)); + } + + // XZ + public void addQuadUp(short x, short y, short z, short wx, short wz, int color, byte skylight, byte blocklight) { + quads.add(new Quad(x, y, z, wx, wz, color, skylight, blocklight, LodDirection.UP)); + } + + public void addQuadDown(short x, short y, short z, short wx, short wz, int color, byte skylight, byte blocklight) { + quads.add(new Quad(x, y, z, wx, wz, color, skylight, blocklight, LodDirection.DOWN)); + } + + // XY + public void addQuadN(short x, short y, short z, short wx, short wy, int color, byte skylight, byte blocklight) { + quads.add(new Quad(x, y, z, wx, wy, color, skylight, blocklight, LodDirection.NORTH)); + } + + public void addQuadS(short x, short y, short z, short wx, short wy, int color, byte skylight, byte blocklight) { + quads.add(new Quad(x, y, z, wx, wy, color, skylight, blocklight, LodDirection.SOUTH)); + } + + // ZY + public void addQuadW(short x, short y, short z, short wz, short wy, int color, byte skylight, byte blocklight) { + quads.add(new Quad(x, y, z, wz, wy, color, skylight, blocklight, LodDirection.WEST)); + } + + public void addQuadE(short x, short y, short z, short wz, short wy, int color, byte skylight, byte blocklight) { + quads.add(new Quad(x, y, z, wz, wy, color, skylight, blocklight, LodDirection.EAST)); + } + + private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight) { + skylight %= 16; + blocklight %= 16; + + bb.putShort(x); + bb.putShort(y); + bb.putShort(z); + bb.putShort((short) (skylight | (blocklight << 4))); + byte r = (byte) ColorUtil.getRed(color); + byte g = (byte) ColorUtil.getGreen(color); + byte b = (byte) ColorUtil.getBlue(color); + byte a = (byte) ColorUtil.getAlpha(color); + bb.put(r); + bb.put(g); + bb.put(b); + bb.put(a); + } + + private static void putQuad(ByteBuffer bb, Quad quad) { + int[][] quadBase = DIRECTION_VERTEX_QUAD[quad.dir.ordinal()]; + short d0 = quad.w0; + short d1 = quad.w1; + Axis axis = quad.dir.getAxis(); + for (int i = 0; i < quadBase.length; i++) { + short dx, dy, dz; + switch (axis) { + case X: // ZY + dx = 0; + dz = quadBase[i][0] == 1 ? d0 : 0; + dy = quadBase[i][1] == 1 ? d1 : 0; + break; + case Y: // XZ + dy = 0; + dx = quadBase[i][0] == 1 ? d0 : 0; + dz = quadBase[i][1] == 1 ? d1 : 0; + break; + case Z: // XY + dz = 0; + dx = quadBase[i][0] == 1 ? d0 : 0; + dy = quadBase[i][1] == 1 ? d1 : 0; + break; + default: + throw new IllegalArgumentException("Invalid Axis enum: " + axis); + } + putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz), quad.color, + quad.skylight, quad.blocklight); + } + + } + + private ByteBuffer writeVertexData(ByteBuffer bb, int quadsStart, int quadsCount) { + if (quadsStart + quadsCount > quads.size()) + quadsCount = quads.size() - quadsStart; + bb.clear(); + bb.limit(quadsCount * QUAD_BYTE_SIZE); + for (Quad quad : quads.subList(quadsStart, quadsStart + quadsCount)) { + putQuad(bb, quad); + } + if (bb.hasRemaining()) + throw new RuntimeException(); + bb.rewind(); + return bb; + } + + public Iterator makeVertexBuffers() { + int numOfBuffers = getCurrentNeededVertexBuffers(); + return new Iterator() { + int counter = 0; + ByteBuffer bb = ByteBuffer.allocateDirect(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE) + .order(ByteOrder.nativeOrder()); + + @Override + public boolean hasNext() { + return counter < numOfBuffers; + } + + @Override + public ByteBuffer next() { + if (counter >= numOfBuffers) { + return null; + } + return writeVertexData(bb, MAX_QUADS_PER_BUFFER * counter++, MAX_QUADS_PER_BUFFER); + } + }; + } + + public int getCurrentNeededVertexBuffers() { + return quads.size() / MAX_QUADS_PER_BUFFER + 1; + } + + public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][] { + // X,Z + { // UP + { 1, 0 }, // 0 + { 1, 1 }, // 1 + { 0, 1 }, // 2 + + { 1, 0 }, // 0 + { 0, 1 }, // 2 + { 0, 0 }, // 3 + }, { // DOWN + { 0, 0 }, // 0 + { 0, 1 }, // 1 + { 1, 1 }, // 2 + + { 0, 0 }, // 0 + { 1, 1 }, // 2 + { 1, 0 }, // 3 + }, + // X,Y + { // NORTH + { 0, 0 }, // 0 + { 0, 1 }, // 1 + { 1, 1 }, // 2 + + { 0, 0 }, // 0 + { 1, 1 }, // 2 + { 1, 0 }, // 3 + }, { // SOUTH + { 1, 0 }, // 0 + { 1, 1 }, // 1 + { 0, 1 }, // 2 + + { 1, 0 }, // 0 + { 0, 1 }, // 2 + { 0, 0 }, // 3 + }, + // Z,Y + { // WEST + { 0, 0 }, // 0 + { 1, 0 }, // 1 + { 1, 1 }, // 2 + + { 0, 0 }, // 0 + { 1, 1 }, // 2 + { 0, 1 }, // 3 + }, { // EAST + { 0, 1 }, // 0 + { 1, 1 }, // 1 + { 1, 0 }, // 2 + + { 0, 1 }, // 0 + { 1, 0 }, // 2 + { 0, 0 }, // 3 + }, }; + +} diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java new file mode 100644 index 000000000..4da36d039 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java @@ -0,0 +1,50 @@ +package com.seibel.lod.core.objects.opengl; + +public class RenderRegion implements AutoCloseable { + LodVertexBuffer[] vbos; + + public RenderRegion(int size) { + vbos = new LodVertexBuffer[size]; + } + + public void resize(int size) { + if (vbos.length != size) { + LodVertexBuffer[] newVbos = new LodVertexBuffer[size]; + if (vbos.length > size) { + for (int i=size; i vbos = lodBufferBuilderFactory.getFrontBuffers(); + MovableGridList regions = lodBufferBuilderFactory.getFrontBuffers(); int vbosCenterX = lodBufferBuilderFactory.getFrontBuffersCenterX(); int vbosCenterZ = lodBufferBuilderFactory.getFrontBuffersCenterZ(); - if (vbos == null) { + if (regions == null) { // There is no vbos, which means nothing needs to be drawn. So skip rendering return; } @@ -282,7 +285,6 @@ public class LodRenderer /*---------Get required data--------*/ // Get the matrixs for rendering - Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks, vbosCenterX, vbosCenterZ); int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH; int farPlaneBlockDistance; // required for setupFog and setupProjectionMatrix @@ -290,15 +292,16 @@ public class LodRenderer farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH; else farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH; - Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, farPlaneBlockDistance); LodFogConfig fogSettings = new LodFogConfig(CONFIG, REFLECTION_HANDLER, farPlaneBlockDistance, vanillaBlockRenderedDistance); drawCalculateParams.end("drawCalculateParams"); + Mat4f projectionMatrix = createProjectionMatrix(baseProjectionMatrix, vanillaBlockRenderedDistance, farPlaneBlockDistance); /*---------Fill uniform data--------*/ LagSpikeCatcher drawFillData = new LagSpikeCatcher(); // Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0 - shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(vbosCenterX, vbosCenterZ), - MC_RENDER.isFogStateSpecial() ? getSpecialFogColor(partialTicks) : getFogColor(partialTicks), (int) (MC.getSkyDarken(partialTicks) * 15), 0); + shaderProgram.fillUniformData(projectionMatrix, + MC_RENDER.isFogStateSpecial() ? getSpecialFogColor(partialTicks) : getFogColor(partialTicks), + (int) (MC.getSkyDarken(partialTicks) * 15), 0); // Previous guy said fog setting may be different from region to region, but the fogSettings never changed... soooooo... shaderProgram.fillUniformDataForFog(fogSettings, MC_RENDER.isFogStateSpecial()); // Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one. @@ -317,16 +320,28 @@ public class LodRenderer // where the center of the buffers is (needed when culling regions) // render each of the buffers - int lowRegionX = vbos.getCenterX() - vbos.gridCentreToEdge; - int lowRegionZ = vbos.getCenterY() - vbos.gridCentreToEdge; + int lowRegionX = regions.getCenterX() - regions.gridCentreToEdge; + int lowRegionZ = regions.getCenterY() - regions.gridCentreToEdge; int drawCall = 0; int vCount0 = 0; - for (int regionX=lowRegionX; regionX= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length) && !vanillaRenderedChunks[tempX][tempZ])) return true; @@ -410,7 +409,7 @@ public class LodUtil } public static boolean isBorderChunk(MovableGridList vanillaRenderedChunks, int chunkX, int chunkZ) { - for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) { int tempX = chunkX + lodDirection.getNormal().x; int tempZ = chunkZ + lodDirection.getNormal().z; diff --git a/src/main/resources/shaders/flat_shaded.frag b/src/main/resources/shaders/flat_shaded.frag index d78fab118..aa696ec26 100644 --- a/src/main/resources/shaders/flat_shaded.frag +++ b/src/main/resources/shaders/flat_shaded.frag @@ -1,15 +1,10 @@ #version 150 core in vec4 vertexColor; -in vec4 vertexWorldPos; - - +in float dist; out vec4 fragColor; - -uniform vec3 cameraPos; - uniform bool fogEnabled; uniform bool nearFogEnabled; uniform bool farFogEnabled; @@ -44,7 +39,6 @@ void main() { // add fog - float dist = distance(vertexWorldPos, vec4(cameraPos,1)); // no fog by default float fogAlpha = 0; diff --git a/src/main/resources/shaders/standard.vert b/src/main/resources/shaders/standard.vert index 44ec8c8d8..4d4510cb8 100644 --- a/src/main/resources/shaders/standard.vert +++ b/src/main/resources/shaders/standard.vert @@ -1,13 +1,10 @@ #version 150 core -in vec3 vPosition; +in vec4 vPosition; in vec4 color; -in vec2 light; out vec4 vertexColor; -out vec4 vertexWorldPos; -out float depth; - +out float dist; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; @@ -21,31 +18,18 @@ uniform sampler2D lightMap; * * author: James Seibel * version: 12-8-2021 + * + * updated: TomTheFurry + * version: 15-2-2022 */ void main() { - float blockSkyLight = light[0]; - float blockLight = light[1]; + vec4 worldSpacePos = modelViewMatrix * vec4(vPosition.xyz,1); + float light = (vPosition.a+0.5) / 256.0; - // just skylight - // good for sanity checks; but will cause OpenGL errors since we are binding unused data -// vertexColor = vec4(color.xyz * worldSkyLight / 16.0, color.w); + vertexColor = color * texture(lightMap, vec2(light,0.5)); - float blockLightTex = blockLight / 16.0; - float skyLightTex = blockSkyLight / 16.0; + dist = length(worldSpacePos.xyz); - // we don't really need alpha in the lightmap -// vertexColor = color * vec4(texture(lightMap, vec2(skyLightTex, blockLightTex)).xyz, 1); - vertexColor = color * texture(lightMap, vec2(skyLightTex, blockLightTex)); - - - - // TODO: add a simple white texture to support Optifine shaders - //textureCoord = textureCoord; - - vertexWorldPos = vec4(vPosition, 1); - - // the vPosition needs to be converted to a vec4 so it can be multiplied - // by the 4x4 matrices - gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1); + gl_Position = projectionMatrix * worldSpacePos; }