Replace the old render logic with VBO rendering

This commit is contained in:
James Seibel
2021-02-27 11:31:53 -06:00
parent 336cfb0749
commit 58b0eafe29
2 changed files with 149 additions and 277 deletions
@@ -1,25 +1,22 @@
package com.backsun.lod.builders;
import java.awt.Color;
import java.util.concurrent.Callable;
import org.lwjgl.opengl.GL11;
import com.backsun.lod.objects.NearFarBuffer;
import com.backsun.lod.renderer.LodRenderer;
import com.backsun.lod.util.enums.FogDistance;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.math.AxisAlignedBB;
/**
* This object is used to create NearFarBuffer objects
* in a thread independent way, so multiple of these objects can be
* created and executed in parallel to populate BufferBuilders.
* This object is used to create NearFarBuffer objects.
*
* @author James Seibel
* @version 02-23-2021
* @version 02-27-2021
*/
public class BuildBufferThread implements Callable<NearFarBuffer>
public class LodBufferBuilder
{
public BufferBuilder nearBuffer;
public BufferBuilder farBuffer;
@@ -27,29 +24,19 @@ public class BuildBufferThread implements Callable<NearFarBuffer>
public AxisAlignedBB[][] lods;
public Color[][] colors;
private int startLodIndex = 0;
private int endLodIndex = -1;
public BuildBufferThread()
public LodBufferBuilder()
{
}
public BuildBufferThread(BufferBuilder newNearBufferBuilder,
BufferBuilder newFarBufferBuilder, AxisAlignedBB[][] newLods,
Color[][] newColors, FogDistance newDistanceMode, int newStartingIndex,
int numberOfRowsToGenerate)
{
setNewData(newNearBufferBuilder, newFarBufferBuilder, distanceMode,
newLods, newColors, newStartingIndex, numberOfRowsToGenerate);
}
public void setNewData(BufferBuilder newNearBufferBuilder,
BufferBuilder newFarBufferBuilder, FogDistance newDistanceMode,
AxisAlignedBB[][] newLods, Color[][] newColors,
int newStartingIndex, int numberOfRowsToGenerate)
public NearFarBuffer createBuffers(
BufferBuilder newNearBufferBuilder, BufferBuilder newFarBufferBuilder,
FogDistance newDistanceMode,
AxisAlignedBB[][] newLods, Color[][] newColors)
{
nearBuffer = newNearBufferBuilder;
farBuffer = newFarBufferBuilder;
@@ -57,15 +44,9 @@ public class BuildBufferThread implements Callable<NearFarBuffer>
lods = newLods;
colors = newColors;
startLodIndex = newStartingIndex;
endLodIndex = newStartingIndex + numberOfRowsToGenerate;
}
@Override
public NearFarBuffer call()
{
nearBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
farBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
nearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
farBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
int numbChunksWide = lods.length;
@@ -90,7 +71,7 @@ public class BuildBufferThread implements Callable<NearFarBuffer>
// x axis
for (int i = startLodIndex; i < endLodIndex; i++)
for (int i = 0; i < numbChunksWide; i++)
{
// z axis
for (int j = 0; j < numbChunksWide; j++)
@@ -4,17 +4,12 @@ import java.awt.Color;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.lwjgl.opengl.GL11;
import com.backsun.lod.builders.BuildBufferThread;
import com.backsun.lod.builders.LodBufferBuilder;
import com.backsun.lod.handlers.ReflectionHandler;
import com.backsun.lod.objects.LodChunk;
import com.backsun.lod.objects.LodDimension;
@@ -24,6 +19,7 @@ import com.backsun.lod.util.enums.ColorDirection;
import com.backsun.lod.util.enums.FogDistance;
import com.backsun.lod.util.enums.FogQuality;
import com.backsun.lod.util.enums.LodCorner;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
@@ -31,12 +27,15 @@ import net.minecraft.client.entity.player.ClientPlayerEntity;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
/**
* @author James Seibel
@@ -56,40 +55,33 @@ public class LodRenderer
private GameRenderer gameRender;
private IProfiler profiler;
private float farPlaneDistance;
// make sure this is an even number, or else it won't align with the chunk grid
/** this is the total width of the LODs (I.E the diameter, not the radius) */
/** this is the radius of the LODs */
private static final int LOD_CHUNK_DISTANCE_RADIUS = 6;
private Tessellator tessellator;
private BufferBuilder bufferBuilder;
private ReflectionHandler reflectionHandler;
public LodDimension lodDimension = null;
/** Total number of CPU cores available to the Java VM */
private int maxNumbThreads = Runtime.getRuntime().availableProcessors();
/** How many threads should be used for building render buffers */
private int numbBufferThreads = maxNumbThreads;
/** This stores all the BuildBufferThread objects for each CPU core */
private ArrayList<BuildBufferThread> bufferThreads = new ArrayList<BuildBufferThread>();
/** This is used to generate the buildable buffers */
private LodBufferBuilder lodBufferBuilder = null;
/** The buffers that are used to draw LODs using near fog */
private volatile BufferBuilder[] drawableNearBuffers = null;
private volatile BufferBuilder drawableNearBuffer = null;
/** The buffers that are used to draw LODs using far fog */
private volatile BufferBuilder[] drawableFarBuffers = null;
private volatile BufferBuilder drawableFarBuffer = null;
/** The buffers that are used to create LODs using near fog */
private volatile BufferBuilder[] buildableNearBuffers = null;
private volatile BufferBuilder buildableNearBuffer = null;
/** The buffers that are used to create LODs using far fog */
private volatile BufferBuilder[] buildableFarBuffers = null;
private volatile BufferBuilder buildableFarBuffer = null;
/** If we have more CPU cores than LOD rows to draw this tells
* which drawable buffers will and won't be used. */
private boolean[] shouldDrawBuffer = new boolean[maxNumbThreads];
/** This is the VertexBuffer used to draw any LODs that use near fog */
private volatile VertexBuffer nearVbo = null;
/** This is the VertexBuffer used to draw any LODs that use far fog */
private volatile VertexBuffer farVbo = null;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
/** This holds the threads used to generate the LOD buffers */
private ExecutorService bufferThreadPool = Executors.newFixedThreadPool(maxNumbThreads);
/** This holds the thread used to generate new LODs off the main thread. */
private ExecutorService genThread = Executors.newSingleThreadExecutor();
@@ -122,10 +114,6 @@ public class LodRenderer
mc = Minecraft.getInstance();
gameRender = mc.gameRenderer;
// for some reason "Tessellator.getInstance()" won't work here, we have to create a new one
tessellator = new Tessellator(2097152); // the number here is what is used by the default Tessellator
bufferBuilder = tessellator.getBuffer();
reflectionHandler = new ReflectionHandler();
}
@@ -137,6 +125,7 @@ public class LodRenderer
* @param newDimension The dimension to draw, if null doesn't replace the current dimension.
* @param partialTicks how far into the current tick this method was called.
*/
@SuppressWarnings("deprecation")
public void drawLODs(LodDimension newDimension, float partialTicks, IProfiler newProfiler)
{
if (lodDimension == null && newDimension == null)
@@ -150,15 +139,17 @@ public class LodRenderer
//===============//
// initial setup //
//===============//
// used for debugging and viewing how long different processes take
mc.getProfiler().endSection();
mc.getProfiler().startSection("LOD");
mc.getProfiler().startSection("LOD setup");
profiler = newProfiler;
profiler.endSection();
profiler.startSection("LOD");
profiler.startSection("LOD setup");
ClientPlayerEntity player = mc.player;
@@ -184,7 +175,6 @@ public class LodRenderer
regen = false;
}
profiler = newProfiler;
lodDimension = newDimension;
if (lodDimension == null)
{
@@ -208,16 +198,6 @@ public class LodRenderer
// get the camera location
ActiveRenderInfo renderInfo = mc.gameRenderer.getActiveRenderInfo();
Vector3d projectedView = renderInfo.getProjectedView();
double cameraX = projectedView.x;
double cameraY = projectedView.y;
double cameraZ = projectedView.z;
// determine how far the game's render distance is currently set
int renderDistWidth = mc.gameSettings.renderDistanceChunks;
farPlaneDistance = renderDistWidth * LodChunk.WIDTH;
@@ -242,15 +222,14 @@ public class LodRenderer
// (this is to prevent thread conflicts)
if (regen && !regenerating && !switchBuffers)
{
mc.getProfiler().endStartSection("LOD generation");
profiler.endStartSection("LOD generation");
regenerating = true;
// this will only be called once, unless the numbBufferThreads changes
if (numbBufferThreads != bufferThreads.size())
setupBufferThreads();
if (lodBufferBuilder == null)
lodBufferBuilder = new LodBufferBuilder();
// this will mainly happen when the view distance is changed
if (drawableNearBuffers == null || drawableFarBuffers == null ||
if (drawableNearBuffer == null || drawableFarBuffer == null ||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks)
setupBuffers(numbChunksWide);
@@ -276,20 +255,17 @@ public class LodRenderer
//===========================//
// set the required open GL settings
// GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
// GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// GL11.glDisable(GL11.GL_TEXTURE_2D);
// GL11.glEnable(GL11.GL_CULL_FACE);
// GL11.glEnable(GL11.GL_COLOR_MATERIAL);
//
// GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
RenderSystem.pushMatrix();
RenderSystem.rotatef(renderInfo.getPitch(), 1, 0, 0); // Fixes camera rotation.
RenderSystem.rotatef(renderInfo.getYaw() + 180, 0, 1, 0); // Fixes camera rotation.
RenderSystem.translated(-cameraX, -cameraY, -cameraZ);
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
// setupProjectionMatrix(partialTicks);
Matrix4f modelViewMatrix = generateModelViewMatrix();
setupProjectionMatrix(partialTicks);
// setupLighting(partialTicks);
@@ -311,25 +287,21 @@ public class LodRenderer
// when drawing NEAR_AND_FAR fog we need 2 draw
// calls since fog can only go in one direction at a time
mc.getProfiler().endStartSection("LOD draw");
setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(drawableNearBuffers);
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
mc.getProfiler().endStartSection("LOD draw");
setupFog(FogDistance.FAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(drawableFarBuffers);
sendLodsToGpuAndDraw(farVbo, modelViewMatrix);
break;
case NEAR:
mc.getProfiler().endStartSection("LOD draw");
setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(drawableNearBuffers);
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
break;
case FAR:
mc.getProfiler().endStartSection("LOD draw");
setupFog(FogDistance.FAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(drawableFarBuffers);
sendLodsToGpuAndDraw(farVbo, modelViewMatrix);
break;
}
@@ -340,20 +312,16 @@ public class LodRenderer
// cleanup //
//=========//
mc.getProfiler().endStartSection("LOD cleanup");
profiler.endStartSection("LOD cleanup");
// this must be done otherwise other parts of the screen may be drawn with a fog effect
// IE the GUI
RenderSystem.disableFog();
// GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
// GL11.glEnable(GL11.GL_TEXTURE_2D);
// GL11.glDisable(LOD_GL_LIGHT_NUMBER);
// GL11.glDisable(GL11.GL_COLOR_MATERIAL);
// undo any projection matrix changes we did to prevent other renders
// from being corrupted
RenderSystem.popMatrix();
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
GL11.glDisable(GL11.GL_COLOR_MATERIAL);
// this can't be called until after the buffers are built
@@ -362,34 +330,52 @@ public class LodRenderer
// end of profiler tracking
mc.getProfiler().endSection();
profiler.endSection();
}
private Matrix4f generateModelViewMatrix()
{
// get all relevant camera info
ActiveRenderInfo renderInfo = mc.gameRenderer.getActiveRenderInfo();
Vector3d projectedView = renderInfo.getProjectedView();
double cameraX = projectedView.x;
double cameraY = projectedView.y;
double cameraZ = projectedView.z;
// generate the model view matrix
MatrixStack matrixStack = new MatrixStack();
matrixStack.push();
// translate and rotate to the current camera location
matrixStack.rotate(Vector3f.XP.rotationDegrees(renderInfo.getPitch()));
matrixStack.rotate(Vector3f.YP.rotationDegrees(renderInfo.getYaw() + 180));
matrixStack.translate(-cameraX, -cameraY, -cameraZ);
return matrixStack.getLast().getMatrix();
}
/**
* This is where the actual drawing happens.
*
* @param buffers the buffers sent to the GPU to draw
*/
private void sendLodsToGpuAndDraw(BufferBuilder[] buffers)
private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix)
{
for(int i = 0; i < numbBufferThreads; i++)
{
if (shouldDrawBuffer[i])
{
int pos = bufferBuilder.byteBuffer.position();
buffers[i].byteBuffer.position(pos);
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
bufferBuilder.byteBuffer.clear();
// replace the data in bufferBuilder with the data from the given buffer
bufferBuilder.putBulkData(buffers[i].byteBuffer);
tessellator.draw();
bufferBuilder.byteBuffer.clear(); // this is required otherwise nothing is drawn
}
}
if (vbo == null)
return;
profiler.endStartSection("LOD draw setup");
vbo.bindBuffer();
LOD_VERTEX_FORMAT.setupBufferState(0L); // TODO what does this do?
profiler.endStartSection("LOD draw");
vbo.draw(modelViewMatrix, 7); // TODO what is 7?
profiler.endStartSection("LOD draw cleanup");
VertexBuffer.unbindBuffer();
LOD_VERTEX_FORMAT.clearBufferState();
}
@@ -402,6 +388,7 @@ public class LodRenderer
// Setup Functions //
//=================//
@SuppressWarnings("deprecation")
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
{
if(fogQuality == FogQuality.OFF)
@@ -487,6 +474,7 @@ public class LodRenderer
/**
* setup the lighting to be used for the LODs
*/
@SuppressWarnings({ "unused", "deprecation" })
private void setupLighting(float partialTicks)
{
GL11.glEnable(GL11.GL_COLOR_MATERIAL); // set the color to be used as the material (this allows lighting to be enabled)
@@ -507,54 +495,24 @@ public class LodRenderer
RenderSystem.enableLighting();
}
/**
* create the BuildBufferThreads
*/
private void setupBufferThreads()
{
bufferThreads.clear();
for(int i = 0; i < numbBufferThreads; i++)
bufferThreads.add(new BuildBufferThread());
}
/**
* Create all buffers that will be used.
*/
private void setupBuffers(int numbChunksWide)
{
drawableNearBuffers = new BufferBuilder[numbBufferThreads];
drawableFarBuffers = new BufferBuilder[numbBufferThreads];
buildableNearBuffers = new BufferBuilder[numbBufferThreads];
buildableFarBuffers = new BufferBuilder[numbBufferThreads];
// calculate how many chunks wide, at most
// any thread will have to generate
int biggestWidth = -1;
int[] loads = calculateCpuLoadBalance(numbChunksWide, numbBufferThreads);
for(int i : loads)
if (i > biggestWidth)
biggestWidth = i;
// calculate the max amount of storage needed (in bytes)
// by any singular buffer
// NOTE: most buffers won't use the full amount, but this should prevent
// them from needing to allocate more memory (which is a slow progress)
int bufferMaxCapacity = (numbChunksWide * biggestWidth * (6 * 4 * ((3 * 4) + (4 * 4))));
int bufferMaxCapacity = (numbChunksWide * numbChunksWide * (6 * 4 * (3 + 4)));
// (numbChunksWide * numbChunksWide *
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints)))
for(int i = 0; i < numbBufferThreads; i++)
{
// TODO complain or do something when memory is too low
// currently the VM will just crash and complain there is no more memory
// issue #4
drawableNearBuffers[i] = new BufferBuilder(bufferMaxCapacity);
drawableFarBuffers[i] = new BufferBuilder(bufferMaxCapacity);
buildableNearBuffers[i] = new BufferBuilder(bufferMaxCapacity);
buildableFarBuffers[i] = new BufferBuilder(bufferMaxCapacity);
}
// TODO complain or do something when memory is too low
// currently the VM will just crash and complain there is no more memory
// issue #4
drawableNearBuffer = new BufferBuilder(bufferMaxCapacity);
drawableFarBuffer = new BufferBuilder(bufferMaxCapacity);
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
}
@@ -697,84 +655,22 @@ public class LodRenderer
}
}
generateLodBuffers(lodArray, colorArray, LodConfig.COMMON.fogDistance.get());
// generate our new buildable buffers
NearFarBuffer nearFarBuffers = lodBufferBuilder.createBuffers(
buildableNearBuffer, buildableFarBuffer,
LodConfig.COMMON.fogDistance.get(), lodArray, colorArray);
// update our buffers
buildableNearBuffer = nearFarBuffers.nearBuffer;
buildableFarBuffer = nearFarBuffers.farBuffer;
// mark the buildable buffers as ready to swap
regenerating = false;
switchBuffers = true;
});
return t;
}
/**
* draw an array of boxes with the given colors.
* <br><br>
* Currently only one color per box is supported.
*
* @param lods bounding boxes to draw
* @param colors color of each box to draw
*/
private void generateLodBuffers(AxisAlignedBB[][] lods, Color[][] colors, FogDistance fogDistance)
{
List<Future<NearFarBuffer>> bufferFutures = new ArrayList<>();
ArrayList<BuildBufferThread> threadsToRun = new ArrayList<>();
int indexToStart = 0;
int[] threadLoads = calculateCpuLoadBalance(lods.length, numbBufferThreads);
// update the information that the bufferThreads are using
for(int i = 0; i < numbBufferThreads; i++)
{
// if we have more threads than LOD rows to generate
// don't send the threads to the CPU
if (threadLoads[i] != 0)
{
// update this thread with the latest information
bufferThreads.get(i).
setNewData(buildableNearBuffers[i], buildableFarBuffers[i],
fogDistance, lods, colors, indexToStart, threadLoads[i]);
indexToStart += threadLoads[i];
// add this thread to the list of threads we are going to run
threadsToRun.add(bufferThreads.get(i));
shouldDrawBuffer[i] = true;
}
else
{
shouldDrawBuffer[i] = false;
}
}
// run all the bufferThreads and get their results
try
{
bufferFutures = bufferThreadPool.invokeAll(threadsToRun);
}
catch (InterruptedException e)
{
// this should never happen, but just in case
e.printStackTrace();
}
// update our buildable buffers
for(int i = 0; i < numbBufferThreads; i++)
{
// only replace buffers that actually generated something
if (threadLoads[i] != 0)
{
try
{
buildableNearBuffers[i] = bufferFutures.get(i).get().nearBuffer;
buildableFarBuffers[i] = bufferFutures.get(i).get().farBuffer;
}
catch(CancellationException | ExecutionException| InterruptedException e)
{
// this should never happen, but just in case
e.printStackTrace();
}
}
}
}
/**
@@ -782,22 +678,35 @@ public class LodRenderer
*/
private void swapBuffers()
{
for(int i = 0; i < buildableNearBuffers.length; i++)
{
try
{
BufferBuilder tmp = buildableNearBuffers[i];
buildableNearBuffers[i] = drawableNearBuffers[i];
drawableNearBuffers[i] = tmp;
tmp = buildableFarBuffers[i];
buildableFarBuffers[i] = drawableFarBuffers[i];
drawableFarBuffers[i] = tmp;
}
catch(Exception e)
{
e.printStackTrace();
}
try
{
// swap the BufferBuilders
BufferBuilder tmp = buildableNearBuffer;
buildableNearBuffer = drawableNearBuffer;
drawableNearBuffer = tmp;
tmp = buildableFarBuffer;
buildableFarBuffer = drawableFarBuffer;
drawableFarBuffer = tmp;
// bind the buffers with their respective VBOs
if (nearVbo != null)
nearVbo.close();
nearVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
nearVbo.upload(drawableNearBuffer);
if (farVbo != null)
farVbo.close();
farVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
farVbo.upload(drawableFarBuffer);
}
catch(Exception e)
{
e.printStackTrace();
}
}
@@ -816,24 +725,6 @@ public class LodRenderer
}
/**
* This is a simple implementation of the pigeon hole
* principle to try and give each BuildBufferThread a balanced load.
*
* @returns an array of ints where each int is how many rows
* that BuildBufferThread should generate
*/
private int[] calculateCpuLoadBalance(int numbOfItems, int numbOfThreads)
{
int[] cpuLoad = new int[numbOfThreads];
for(int i = 0; i < numbOfItems; i++)
cpuLoad[i % numbOfThreads]++;
return cpuLoad;
}
public double getFov(float partialTicks, boolean useFovSetting)
{
return mc.gameRenderer.getFOVModifier(mc.gameRenderer.getActiveRenderInfo(), partialTicks, useFovSetting);