Clean up LodRendering logic

This commit is contained in:
James Seibel
2025-10-19 16:06:00 -05:00
parent 0e0e1e1b0f
commit f7ece2b02e
20 changed files with 673 additions and 900 deletions
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @since API 2.0.0
* @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
*/
@Deprecated
@Deprecated // internal notes: this method must be kept around due to Iris using it and we don't want to break old Iris support
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
{
/** Fired before Distant Horizons creates. */
@@ -38,7 +38,7 @@ public class DhApiRenderParam implements IDhApiEventParam
/** Indicates how far into this tick the frame is. */
public final float partialTicks;
/**
/**
* Indicates DH's near clip plane, measured in blocks.
* Note: this may change based on time, player speed, and other factors.
*/
@@ -67,7 +67,6 @@ public class DhApiRenderParam implements IDhApiEventParam
// constructors //
//==============//
public DhApiRenderParam(DhApiRenderParam parent)
{
this(
@@ -86,7 +85,7 @@ public class DhApiRenderParam implements IDhApiEventParam
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
int worldYOffset
)
)
{
this.renderPass = renderPass;
@@ -111,10 +110,9 @@ public class DhApiRenderParam implements IDhApiEventParam
// base overrides //
//================//
@Override
public DhApiRenderParam copy()
{
return new DhApiRenderParam(this);
}
@Override
public DhApiRenderParam copy() { return new DhApiRenderParam(this); }
}
@@ -23,8 +23,7 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.api.internal.rendering.RenderState;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
@@ -32,11 +31,11 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.RenderParams;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession;
@@ -45,10 +44,8 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
@@ -57,15 +54,14 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* This holds the methods that should be called
@@ -95,14 +91,13 @@ public class ClientApi
*
* Only downside is making sure each variable is populated before rendering.
*/
public static final RenderState RENDER_STATE = new RenderState();
public static final DhRenderState RENDER_STATE = new DhRenderState();
private boolean isDevBuildMessagePrinted = false;
private boolean lowMemoryWarningPrinted = false;
private boolean highVanillaRenderDistanceWarningPrinted = false;
/** when the last static */
private long lastStaticWarningMessageSentMsTime = 0L;
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
@@ -110,8 +105,6 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false;
private long lastFlushNanoTime = 0;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
/** Delay loading the first level to give the server some time to respond with level to actually load */
@@ -123,8 +116,8 @@ public class ClientApi
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>();
/** re-set every frame during the opaque rendering stage */
private boolean renderingCancelledForThisFrame;
@Nullable
public String lastRenderParamValidationMessage = null;
@@ -333,6 +326,7 @@ public class ClientApi
// clint tick //
//============//
@Deprecated
public void clientTickEvent()
{
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
@@ -340,7 +334,7 @@ public class ClientApi
try
{
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
IDhClientWorld clientWorld = SharedApi.tryGetDhClientWorld();
if (clientWorld != null)
{
clientWorld.clientTick();
@@ -383,9 +377,9 @@ public class ClientApi
//===========//
// rendering //
//===========//
//===============//
// LOD rendering //
//===============//
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); }
@@ -398,18 +392,9 @@ public class ClientApi
private void renderLodLayer(boolean renderingDeferredLayer)
{
// A global render state variable is used since MC has split up their
// render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment
IClientLevelWrapper levelWrapper = RENDER_STATE.clientLevelWrapper;
Mat4f mcModelViewMatrix = RENDER_STATE.mcModelViewMatrix;
Mat4f mcProjectionMatrix = RENDER_STATE.mcProjectionMatrix;
float partialTicks = RENDER_STATE.frameTime;
//=========//
// logging //
//=========//
this.sendQueuedChatMessages();
@@ -419,7 +404,33 @@ public class ClientApi
// render parameter setup //
//=====================//
// render thread tasks //
//=====================//
// only run these tasks once per frame
if (!renderingDeferredLayer)
{
profiler.push("DH render thread tasks");
try
{
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks();
}
catch (Exception e)
{
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
}
profiler.pop();
}
//=================//
// parameter setup //
//=================//
EDhApiRenderPass renderPass;
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
@@ -438,86 +449,67 @@ public class ClientApi
renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT;
}
DhApiRenderParam renderEventParam =
new DhApiRenderParam(
renderPass,
partialTicks,
RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
mcProjectionMatrix, mcModelViewMatrix,
RenderUtil.createLodProjectionMatrix(mcProjectionMatrix, partialTicks), RenderUtil.createLodModelViewMatrix(mcModelViewMatrix),
levelWrapper.getMinHeight()
);
// A global render state variable is used since MC has split up their
// render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment
RenderParams renderParams =
new RenderParams(
renderPass,
RENDER_STATE.frameTime,
RENDER_STATE.mcProjectionMatrix, RENDER_STATE.mcModelViewMatrix,
RENDER_STATE.clientLevelWrapper
);
//Mat4f mcCombined = mcModelViewMatrix.copy();
//mcCombined.multiply(mcProjectionMatrix);
//
//com.seibel.distanthorizons.api.objects.math.DhApiMat4f dhCombined = renderEventParam.dhModelViewMatrix.copy();
//dhCombined.multiply(renderEventParam.dhProjectionMatrix);
//
//LOGGER.info("\n\n" +
// "API\n" +
// "Mc MVM: \n" + mcModelViewMatrix.toString() + "\n" +
// "Mc Proj: \n" + mcProjectionMatrix + "\n" +
// "Mc Combined:\n" + mcCombined.toString() + "\n" +
// "\n" +
// "DH MVM: \n" + renderEventParam.dhModelViewMatrix.toString() + "\n" +
// "DH Proj: \n" + renderEventParam.dhProjectionMatrix + "\n" +
// "DH Combined:\n" + mcCombined.toString()
//);
//============//
// validation //
//============//
// TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
String validationMessage = renderParams.getValidationErrorMessage();
if (validationMessage != null)
{
this.lastRenderParamValidationMessage = validationMessage;
return;
}
else
{
this.lastRenderParamValidationMessage = null;
}
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
// render validation //
//===========//
// rendering //
//===========//
try
{
// TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
String reasonLodsCannotRender = RenderUtil.shouldLodsRender(levelWrapper, renderEventParam);
if (reasonLodsCannotRender != null)
{
return;
}
IDhClientWorld dhClientWorld = SharedApi.getIDhClientWorld();
if (dhClientWorld == null)
{
return;
}
IDhClientLevel level = (IDhClientLevel) dhClientWorld.getLevel(levelWrapper);
if (level == null)
{
return;
}
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
// render pass //
if (!renderingDeferredLayer)
{
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{
this.renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderEventParam);
if (!this.renderingCancelledForThisFrame)
boolean renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams);
if (!renderingCancelledForThisFrame)
{
LodRenderer.INSTANCE.render(level, levelWrapper, renderEventParam, profiler);
LodRenderer.INSTANCE.render(renderParams, profiler);
}
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
@@ -534,10 +526,10 @@ public class ClientApi
}
else
{
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderEventParam);
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
if (!renderingCancelled)
{
LodRenderer.INSTANCE.renderDeferred(level, levelWrapper, renderEventParam, profiler);
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
}
@@ -557,24 +549,19 @@ public class ClientApi
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
}
finally
{
try
{
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks();
}
catch (Exception e)
{
LOGGER.error("Unexpected issue running render thread tasks.", e);
}
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
}
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
}
//================//
// fade rendering //
//================//
/**
* The first fade pass.
* Called after MC finishes rendering the opaque passes.
@@ -619,7 +606,6 @@ public class ClientApi
//=================//
// DEBUG USE //
//=================//
@@ -61,7 +61,7 @@ public class ServerApi
{
try
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
if (serverWorld != null)
{
serverWorld.serverTick();
@@ -152,7 +152,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] joined.");
if (serverWorld != null)
{
@@ -166,7 +166,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] disconnected.");
if (serverWorld != null)
{
@@ -180,7 +180,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"].");
if (serverWorld != null)
{
@@ -200,7 +200,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
if (serverWorld != null)
{
serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message);
@@ -153,12 +153,14 @@ public class SharedApi
@Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
public static IDhClientWorld getIDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
@Nullable
public static IDhClientWorld tryGetDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
public static IDhServerWorld getIDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
@Nullable
public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
*
* @see ClientApi
*/
public class RenderState
public class DhRenderState
{
public Mat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null;
@@ -1061,14 +1061,6 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> validateBufferIdsBeforeRendering = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Massively reduces FPS. \n"
+ "Should only be used if mysterious EXCEPTION_ACCESS_VIOLATION crashes are happening in DH's rendering code for troubleshooting. \n"
+ "")
.build();
}
public static class ColumnBuilderDebugging
@@ -55,14 +55,15 @@ public class ColumnRenderBufferBuilder
//==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync(
public static CompletableFuture<LodBufferContainer> uploadBuffersAsync(
IDhClientLevel clientLevel,
long pos,
LodQuadBuilder quadBuilder
)
{
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos)));
CompletableFuture<ColumnRenderBuffer> uploadFuture = buffer.makeAndUploadBuffersAsync(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.makeAndUploadBuffersAsync(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
uploadFuture.whenComplete((uploadedBuffer, exception) ->
{
// clean up if not uploaded
@@ -19,13 +19,11 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
@@ -40,7 +38,7 @@ import java.util.concurrent.*;
*
* @see ColumnRenderBufferBuilder
*/
public class ColumnRenderBuffer implements AutoCloseable
public class LodBufferContainer implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -54,13 +52,14 @@ public class ColumnRenderBuffer implements AutoCloseable
/** the position closest to minimum X/Z infinity and the level's lowest Y */
public final DhBlockPos minCornerBlockPos;
public final long pos;
public boolean buffersUploaded = false;
private GLVertexBuffer[] vbos;
private GLVertexBuffer[] vbosTransparent;
public GLVertexBuffer[] vbos;
public GLVertexBuffer[] vbosTransparent;
private CompletableFuture<ColumnRenderBuffer> uploadFuture = null;
private CompletableFuture<LodBufferContainer> uploadFuture = null;
@@ -68,8 +67,9 @@ public class ColumnRenderBuffer implements AutoCloseable
// constructors //
//==============//
public ColumnRenderBuffer(DhBlockPos minCornerBlockPos)
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
{
this.pos = pos;
this.minCornerBlockPos = minCornerBlockPos;
this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0];
@@ -82,10 +82,10 @@ public class ColumnRenderBuffer implements AutoCloseable
//==================//
/** Should be run on a DH thread. */
public synchronized CompletableFuture<ColumnRenderBuffer> makeAndUploadBuffersAsync(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod)
public synchronized CompletableFuture<LodBufferContainer> makeAndUploadBuffersAsync(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod)
{
// separate variable to prevent race condition when checking null
CompletableFuture<ColumnRenderBuffer> future = this.uploadFuture;
CompletableFuture<LodBufferContainer> future = this.uploadFuture;
if (future != null)
{
// upload already in process
@@ -222,69 +222,6 @@ public class ColumnRenderBuffer implements AutoCloseable
//========//
// render //
//========//
/** @return true if something was rendered, false otherwise */
public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
renderContext.setModelViewMatrixOffset(this.minCornerBlockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbos)
{
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
hasRendered = true;
renderContext.drawVbo(vbo, this);
}
return hasRendered;
}
/** @return true if something was rendered, false otherwise */
public boolean renderTransparent(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
try
{
// can throw an IllegalStateException if the GL program was freed before it should've been
renderContext.setModelViewMatrixOffset(this.minCornerBlockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
hasRendered = true;
renderContext.drawVbo(vbo, this);
}
}
catch (IllegalStateException e)
{
LOGGER.error("renderContext program doesn't exist for pos: "+this.minCornerBlockPos, e);
}
return hasRendered;
}
//================//
// helper methods //
//================//
@@ -280,7 +280,7 @@ public class LodQuadBuilder
// create a new buffer
if (buffer == null || !buffer.hasRemaining())
{
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
buffer = MemoryUtil.memAlloc(LodBufferContainer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer);
}
@@ -451,7 +451,7 @@ public class LodQuadBuilder
}
/** Returns how many GpuBuffers will be needed to render opaque quads in this builder. */
public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), ColumnRenderBuffer.MAX_QUADS_PER_BUFFER); }
public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER); }
/** Returns how many GpuBuffers will be needed to render transparent quads in this builder. */
public int getCurrentNeededTransparentVertexBufferCount()
{
@@ -460,7 +460,7 @@ public class LodQuadBuilder
return 0;
}
return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), ColumnRenderBuffer.MAX_QUADS_PER_BUFFER);
return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER);
}
}
@@ -38,7 +38,7 @@ public class PregenManager
)
{
PregenState pregenState = new PregenState(
(GeneratedFullDataSourceProvider) SharedApi.getIDhServerWorld().getLevel(levelWrapper).getFullDataProvider(),
(GeneratedFullDataSourceProvider) SharedApi.tryGetDhServerWorld().getLevel(levelWrapper).getFullDataProvider(),
DhSectionPos.convertToDetailLevel(
DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, origin.x, origin.z),
DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.logging.f3;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
@@ -110,6 +111,13 @@ public class F3Screen
messageList.add("Build: " + StringUtil.shortenString(ModJarInfo.Git_Commit, 8) + " (" + ModJarInfo.Git_Branch + ")");
}
// render validation error
if (ClientApi.INSTANCE.lastRenderParamValidationMessage != null)
{
messageList.add("Render Validation Err: " + ClientApi.INSTANCE.lastRenderParamValidationMessage);
}
// player pos
if (Config.Client.Advanced.Debugging.F3Screen.showPlayerPos.get())
{
@@ -24,7 +24,7 @@ import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.CachedColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
@@ -44,7 +44,6 @@ import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongIterator;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import javax.annotation.WillNotClose;
@@ -353,11 +352,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// onRenderDisabled doesn't need to be
// called since these sections shouldn't be loaded
parentRenderSection.setRenderingEnabled(false);
ColumnRenderBuffer buffer = parentRenderSection.renderBuffer;
LodBufferContainer buffer = parentRenderSection.bufferContainer;
if (buffer != null)
{
buffer.close();
parentRenderSection.renderBuffer = null;
parentRenderSection.bufferContainer = null;
}
}
}
@@ -398,7 +397,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// prepare this section for rendering
if (!renderSection.gpuUploadInProgress()
&& renderSection.renderBuffer == null
&& renderSection.bufferContainer == null
// TODO this is commented out since some users reported LODs refusing to
// load at their expected higher-detail levels
// this check is specifically for N-sized world generators where the higher quality
@@ -518,7 +517,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
for (int i = 0; i < loadSectionList.size(); i++)
{
LodRenderSection renderSection = loadSectionList.get(i);
if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null)
if (!renderSection.gpuUploadInProgress() && renderSection.bufferContainer == null)
{
renderSection.uploadRenderDataToGpuAsync();
}
@@ -768,14 +767,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{
color = Color.ORANGE;
}
else if (renderSection.renderBuffer == null)
else if (renderSection.bufferContainer == null)
{
// uploaded but the buffer is missing
color = Color.PINK;
}
else if (renderSection.renderBuffer.hasNonNullVbos())
else if (renderSection.bufferContainer.hasNonNullVbos())
{
if (renderSection.renderBuffer.vboBufferCount() != 0)
if (renderSection.bufferContainer.vboBufferCount() != 0)
{
color = Color.GREEN;
}
@@ -39,7 +39,7 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
@@ -50,7 +50,6 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import javax.annotation.WillNotClose;
@@ -97,7 +96,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
private boolean renderingEnabled = false;
/** this reference is necessary so we can determine what VBO to render */
public ColumnRenderBuffer renderBuffer;
public LodBufferContainer bufferContainer;
/**
@@ -119,7 +118,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
* Separate from {@link LodRenderSection#getAndBuildRenderDataFuture} because they run on
* different threads (buffer uploading is on the MC render thread) and need to be canceled separately.
*/
private CompletableFuture<ColumnRenderBuffer> bufferUploadFuture = null;
private CompletableFuture<LodBufferContainer> bufferUploadFuture = null;
/**
* should be an empty array if no positions need to be generated
@@ -400,15 +399,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.bufferUploadFuture.thenAccept((buffer) ->
{
// needed to clean up the old data
ColumnRenderBuffer previousBuffer = this.renderBuffer;
LodBufferContainer previousContainer = this.bufferContainer;
// upload complete
this.renderBuffer = buffer.buffersUploaded ? buffer : null;
this.bufferContainer = buffer.buffersUploaded ? buffer : null;
this.getAndBuildRenderDataFuture = null;
if (previousBuffer != null)
if (previousContainer != null)
{
previousBuffer.close();
previousContainer.close();
}
});
}
@@ -419,7 +418,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// getters and properties //
//========================//
public boolean canRender() { return this.renderBuffer != null; }
public boolean canRender() { return this.bufferContainer != null; }
public boolean getRenderingEnabled() { return this.renderingEnabled; }
/**
@@ -683,9 +682,9 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.stopRenderingBeacons();
if (this.renderBuffer != null)
if (this.bufferContainer != null)
{
this.renderBuffer.close();
this.bufferContainer.close();
}
// removes any in-progress futures since they aren't needed any more
@@ -705,7 +704,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
}
}
CompletableFuture<ColumnRenderBuffer> uploadFuture = this.bufferUploadFuture;
CompletableFuture<LodBufferContainer> uploadFuture = this.bufferUploadFuture;
if (uploadFuture != null)
{
uploadFuture.cancel(true);
@@ -24,10 +24,9 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullin
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShadowCullingFrustum;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
@@ -35,8 +34,7 @@ import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.render.renderer.RenderParams;
import com.seibel.distanthorizons.core.util.objects.SortedArraySet;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
@@ -46,20 +44,15 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL32;
import java.util.Comparator;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This object tells the {@link LodRenderer} what buffers to render
* TODO rename this class, maybe RenderBufferOrganizer or something more specific?
*/
public class RenderBufferHandler implements AutoCloseable
{
@@ -74,7 +67,8 @@ public class RenderBufferHandler implements AutoCloseable
public final LodQuadTree lodQuadTree;
// TODO: Make sorting go into the update loop instead of the render loop as it doesn't need to be done every frame
private SortedArraySet<LoadedRenderBuffer> loadedNearToFarBuffers = null;
@Nullable
private SortedArraySet<LodBufferContainer> loadedNearToFarBuffers = null;
private int visibleBufferCount;
private int culledBufferCount;
@@ -113,71 +107,14 @@ public class RenderBufferHandler implements AutoCloseable
/**
* The following buildRenderList sorting method is based on the following reddit post: <br>
* <a href="https://www.reddit.com/r/VoxelGameDev/comments/a0l8zc/correct_depthordering_for_translucent_discrete/">correct_depth_ordering_for_translucent_discrete</a> <br><br>
*
* TODO: This might get locked by update() causing move() call. Is there a way to avoid this?
* Maybe dupe the base list and use atomic swap on render? Or is this not worth it?
* <a href="https://www.reddit.com/r/VoxelGameDev/comments/a0l8zc/correct_depthordering_for_translucent_discrete/">correct_depth_ordering_for_translucent_discrete</a>
*/
public void buildRenderListAndUpdateSections(IClientLevelWrapper clientLevelWrapper, DhApiRenderParam renderEventParam, Vec3f lookForwardVector)
public void buildRenderList(RenderParams renderParams)
{
EDhDirection[] axisDirections = new EDhDirection[3];
// Do the axis that are the longest first (i.e. the largest absolute value of the lookForwardVector),
// with the sign being the opposite of the respective lookForwardVector component's sign
float absX = Math.abs(lookForwardVector.x);
float absY = Math.abs(lookForwardVector.y);
float absZ = Math.abs(lookForwardVector.z);
EDhDirection xDir = lookForwardVector.x < 0 ? EDhDirection.EAST : EDhDirection.WEST;
EDhDirection yDir = lookForwardVector.y < 0 ? EDhDirection.UP : EDhDirection.DOWN;
EDhDirection zDir = lookForwardVector.z < 0 ? EDhDirection.SOUTH : EDhDirection.NORTH;
if (absX >= absY && absX >= absZ)
{
axisDirections[0] = xDir;
if (absY >= absZ)
{
axisDirections[1] = yDir;
axisDirections[2] = zDir;
}
else
{
axisDirections[1] = zDir;
axisDirections[2] = yDir;
}
}
else if (absY >= absX && absY >= absZ)
{
axisDirections[0] = yDir;
if (absX >= absZ)
{
axisDirections[1] = xDir;
axisDirections[2] = zDir;
}
else
{
axisDirections[1] = zDir;
axisDirections[2] = xDir;
}
}
else
{
axisDirections[0] = zDir;
if (absX >= absY)
{
axisDirections[1] = xDir;
axisDirections[2] = yDir;
}
else
{
axisDirections[1] = yDir;
axisDirections[2] = xDir;
}
}
Pos2D centerPos = this.lodQuadTree.getCenterBlockPos().toPos2D();
// Now that we have the axis directions, we can sort the render list
Comparator<LoadedRenderBuffer> farToNearComparator = (loadedBufferA, loadedBufferB) ->
Comparator<LodBufferContainer> farToNearComparator = (loadedBufferA, loadedBufferB) ->
{
Pos2D aPos = DhSectionPos.getCenterBlockPos(loadedBufferA.pos).toPos2D();
Pos2D bPos = DhSectionPos.getCenterBlockPos(loadedBufferB.pos).toPos2D();
@@ -186,7 +123,9 @@ public class RenderBufferHandler implements AutoCloseable
int bManhattanDistance = bPos.manhattanDist(centerPos);
return bManhattanDistance - aManhattanDistance;
};
this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong?
// TODO is the comparator named wrong?
this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b));
@@ -213,17 +152,20 @@ public class RenderBufferHandler implements AutoCloseable
// update the frustum if necessary
if (enableFrustumCulling)
{
int worldMinY = clientLevelWrapper.getMinHeight();
int worldHeight = clientLevelWrapper.getMaxHeight();
int worldMinY = renderParams.clientLevelWrapper.getMinHeight();
int worldHeight = renderParams.clientLevelWrapper.getMaxHeight();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
Matrix4fc matWorldView = new Matrix4f()
.setTransposed(renderEventParam.mcModelViewMatrix.getValuesAsArray())
.translate(-(float) cameraPos.x, -(float) cameraPos.y, -(float) cameraPos.z);
.setTransposed(renderParams.mcModelViewMatrix.getValuesAsArray())
.translate(
-(float) cameraPos.x,
-(float) cameraPos.y,
-(float) cameraPos.z);
Matrix4fc matWorldViewProjection = new Matrix4f()
.setTransposed(renderEventParam.dhProjectionMatrix.getValuesAsArray())
.setTransposed(renderParams.dhProjectionMatrix.getValuesAsArray())
.mul(matWorldView);
frustum.update(worldMinY, worldMinY + worldHeight, new Mat4f(matWorldViewProjection));
@@ -308,14 +250,14 @@ public class RenderBufferHandler implements AutoCloseable
try
{
ColumnRenderBuffer buffer = renderSection.renderBuffer;
if (buffer == null
LodBufferContainer bufferContainer = renderSection.bufferContainer;
if (bufferContainer == null
|| !renderSection.getRenderingEnabled())
{
continue;
}
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos));
this.loadedNearToFarBuffers.add(bufferContainer);
}
catch (Exception e)
{
@@ -339,71 +281,7 @@ public class RenderBufferHandler implements AutoCloseable
// render methods //
//================//
public void renderOpaque(LodRenderer renderContext,DhApiRenderParam renderEventParam)
{ this.renderPass(renderContext, renderEventParam, true); }
public void renderTransparent(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{ this.renderPass(renderContext, renderEventParam, false); }
private void renderPass(LodRenderer renderContext, DhApiRenderParam renderEventParam, boolean opaquePass)
{
//=======================//
// debug wireframe setup //
//=======================//
// TODO move this logic into LodRenderer so all the GL states can be handled there
boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
if (renderWireframe)
{
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
GLMC.disableFaceCulling();
}
else
{
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling();
}
//===========//
// rendering //
//===========//
if (opaquePass)
{
// TODO why can these sometimes be null when teleporting between multiverses
if (this.loadedNearToFarBuffers != null)
{
this.loadedNearToFarBuffers.forEach(loadedBuffer -> loadedBuffer.buffer.renderOpaque(renderContext, renderEventParam));
}
}
else
{
// TODO why can these sometimes be null when teleporting between multiverses
if (this.loadedNearToFarBuffers != null)
{
ListIterator<LoadedRenderBuffer> iter = this.loadedNearToFarBuffers.listIterator(this.loadedNearToFarBuffers.size());
while (iter.hasPrevious())
{
LoadedRenderBuffer loadedBuffer = iter.previous();
loadedBuffer.buffer.renderTransparent(renderContext, renderEventParam);
}
}
}
//=========================//
// debug wireframe cleanup //
//=========================//
if (renderWireframe)
{
// default back to GL_FILL since all other rendering uses it
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling();
}
}
public SortedArraySet<LodBufferContainer> getColumnRenderBuffers() { return this.loadedNearToFarBuffers; }
@@ -447,21 +325,4 @@ public class RenderBufferHandler implements AutoCloseable
//================//
// helper classes //
//================//
private static class LoadedRenderBuffer
{
public final ColumnRenderBuffer buffer;
public final long pos;
LoadedRenderBuffer(ColumnRenderBuffer buffer, long pos)
{
this.buffer = buffer;
this.pos = pos;
}
}
}
@@ -19,16 +19,14 @@
package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiFramebuffer;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
@@ -41,21 +39,19 @@ import com.seibel.distanthorizons.core.render.glObject.texture.*;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.shaders.*;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.objects.SortedArraySet;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* This is where all the magic happens. <br>
* This is where LODs are draw to the world.
@@ -82,20 +78,18 @@ public class LodRenderer
private int activeFramebufferId = -1;
private int activeColorTextureId = -1;
private int activeDepthTextureId = -1;
private int cachedWidth;
private int cachedHeight;
private final ReentrantLock setupLock = new ReentrantLock();
private int textureWidth;
private int textureHeight;
// The shader program
private IDhApiShaderProgram lodRenderProgram = null;
public QuadElementBuffer quadIBO = null;
private boolean isSetupComplete = false;
private boolean renderObjectsCreated = false;
// frameBuffer and texture ID's for this renderer
private IDhApiFramebuffer framebuffer;
/** will be null if MC's framebuffer is being used since MC already has a color texture */
@Nullable
private DhColorTexture nullableColorTexture;
private DHDepthTexture depthTexture;
/**
@@ -110,10 +104,7 @@ public class LodRenderer
// constructor //
//=============//
private LodRenderer()
{
}
private LodRenderer() { }
@@ -126,16 +117,8 @@ public class LodRenderer
* {@link DhApiRenderProxy#getDeferTransparentRendering()} is false,
* otherwise it will only render opaque LODs.
*/
public void render(
IDhClientLevel clientLevel, IClientLevelWrapper clientLevelWrapper,
DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{
this.renderLodPass(
clientLevel, clientLevelWrapper,
renderEventParam,
profiler,
false);
}
public void render(RenderParams renderParams, IProfilerWrapper profiler)
{ this.renderLodPass(renderParams, profiler, false); }
/**
* This method is designed for Iris to be able
@@ -143,59 +126,32 @@ public class LodRenderer
* It needs to be updated with any major changes,
* but shouldn't be activated as per deferWaterRendering.
*/
public void renderDeferred(
IDhClientLevel clientLevel, IClientLevelWrapper clientLevelWrapper,
DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{
this.renderLodPass(
clientLevel, clientLevelWrapper,
renderEventParam,
profiler,
true);
}
public void renderDeferred(RenderParams renderParams, IProfilerWrapper profiler)
{ this.renderLodPass(renderParams, profiler, true); }
private void renderLodPass(
IDhClientLevel clientLevel, IClientLevelWrapper clientLevelWrapper,
DhApiRenderParam renderEventParam,
IProfilerWrapper profiler,
boolean runningDeferredPass)
private void renderLodPass(RenderParams renderParams, IProfilerWrapper profiler, boolean runningDeferredPass)
{
//====================//
// validate rendering //
//====================//
boolean deferTransparentRendering = DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (runningDeferredPass && !deferTransparentRendering)
if (runningDeferredPass
&& !deferTransparentRendering)
{
return;
}
boolean firstPass = !runningDeferredPass;
if (AbstractOptifineAccessor.optifinePresent()
&& MC_RENDER.getTargetFrameBuffer() == -1)
// RenderParams parameter validation should be done before this
if (!renderParams.validationRun)
{
// wait for MC to finish setting up their renderer
return;
throw new IllegalArgumentException("Render parameters validation");
}
ILightMapWrapper lightmap = MC_RENDER.getLightmapWrapper(clientLevelWrapper);
if (lightmap == null)
{
// this shouldn't normally happen, but just in case
return;
}
RenderBufferHandler renderBufferHandler = clientLevel.getRenderBufferHandler();
if (renderBufferHandler == null)
{
return;
}
GenericObjectRenderer genericRenderer = clientLevel.getGenericRenderer();
if (genericRenderer == null)
{
return;
}
RenderBufferHandler renderBufferHandler = renderParams.renderBufferHandler;
GenericObjectRenderer genericRenderer = renderParams.genericRenderer;
ILightMapWrapper lightmap = renderParams.lightmap;
@@ -203,22 +159,39 @@ public class LodRenderer
// rendering setup //
//=================//
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderEventParam);
this.setupGLStateAndRenderObjects(profiler, renderEventParam, firstPass);
if (!this.isSetupComplete)
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams);
profiler.push("LOD GL setup");
if (!this.renderObjectsCreated)
{
// this shouldn't normally happen, but just in case
return;
boolean setupSuccess = this.createRenderObjects();
if (!setupSuccess)
{
// shouldn't normally happen, but just in case
return;
}
this.renderObjectsCreated = true;
}
this.setGLState(renderParams, firstPass);
lightmap.bind();
this.quadIBO.bind();
if (firstPass)
{
renderBufferHandler.buildRenderListAndUpdateSections(clientLevelWrapper, renderEventParam, MC_RENDER.getLookAtVector());
// we only need to sort/cull the LODs during the first frame
profiler.popPush("LOD build render list");
renderBufferHandler.buildRenderList(renderParams);
}
IDhApiShaderProgram lodShaderProgram = this.lodRenderProgram;
IDhApiShaderProgram lodShaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (lodShaderProgramOverride != null && lodShaderProgram.overrideThisFrame())
{
lodShaderProgram = lodShaderProgramOverride;
}
@@ -228,68 +201,57 @@ public class LodRenderer
if (!runningDeferredPass)
{
//=================================//
// opaque (non-deferred) rendering //
//=================================//
//=========================//
// opaque and non-deferred //
// transparent rendering //
//=========================//
// Disable blending for opaque rendering
GLMC.disableBlend();
// terrain
// opaque LODs
profiler.popPush("LOD Opaque");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam);
renderBufferHandler.renderOpaque(this, renderEventParam);
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ true);
// custom objects with SSAO
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
{
profiler.popPush("Custom Objects");
genericRenderer.render(renderEventParam, profiler, true);
genericRenderer.render(renderParams, profiler, true);
}
// SSAO
if (Config.Client.Advanced.Graphics.Ssao.enableSsao.get())
{
profiler.popPush("LOD SSAO");
SSAORenderer.INSTANCE.render(new Mat4f(renderEventParam.dhProjectionMatrix), renderEventParam.partialTicks);
SSAORenderer.INSTANCE.render(new Mat4f(renderParams.dhProjectionMatrix), renderParams.partialTicks);
}
// custom objects without SSAO
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
{
profiler.popPush("Custom Objects");
genericRenderer.render(renderEventParam, profiler, false);
genericRenderer.render(renderParams, profiler, false);
}
// combined pass transparent rendering
if (!deferTransparentRendering
&& Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
{
this.renderTransparentBuffersAndFireApiEvent(profiler, renderEventParam, renderBufferHandler);
profiler.popPush("LOD Transparent");
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ false);
}
// fog
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get())
{
profiler.popPush("LOD Fog");
Mat4f combinedMatrix = new Mat4f(renderEventParam.dhProjectionMatrix);
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
Mat4f combinedMatrix = new Mat4f(renderParams.dhProjectionMatrix);
combinedMatrix.multiply(renderParams.dhModelViewMatrix);
FogRenderer.INSTANCE.render(combinedMatrix, renderEventParam.partialTicks);
FogRenderer.INSTANCE.render(combinedMatrix, renderParams.partialTicks);
}
//=================//
// debug rendering //
//=================//
@@ -298,16 +260,18 @@ public class LodRenderer
{
profiler.popPush("Debug wireframes");
Mat4f combinedMatrix = new Mat4f(renderEventParam.dhProjectionMatrix);
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
Mat4f combinedMatrix = new Mat4f(renderParams.dhProjectionMatrix);
combinedMatrix.multiply(renderParams.dhModelViewMatrix);
// Note: this can be very slow if a lot of boxes are being rendered
DebugRenderer.INSTANCE.render(combinedMatrix);
}
profiler.popPush("LOD cleanup");
//===================//
// optifine clean up //
//===================//
if (this.usingMcFrameBuffer)
{
@@ -322,13 +286,13 @@ public class LodRenderer
// Apply to the MC FrameBuffer //
//=============================//
boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderEventParam);
boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderParams);
if (!cancelApplyShader)
{
profiler.popPush("LOD Apply");
// Copy the LOD framebuffer to Minecraft's framebuffer
DhApplyShader.INSTANCE.render(renderEventParam.partialTicks);
DhApplyShader.INSTANCE.render(renderParams.partialTicks);
}
}
else
@@ -340,18 +304,17 @@ public class LodRenderer
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
{
profiler.popPush("LOD Transparent");
this.renderTransparentBuffersAndFireApiEvent(profiler, renderEventParam, renderBufferHandler);
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ false);
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get())
{
profiler.popPush("LOD Fog");
Mat4f combinedMatrix = new Mat4f(renderEventParam.dhProjectionMatrix);
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
Mat4f combinedMatrix = new Mat4f(renderParams.dhProjectionMatrix);
combinedMatrix.multiply(renderParams.dhModelViewMatrix);
FogRenderer.INSTANCE.render(combinedMatrix, renderEventParam.partialTicks);
FogRenderer.INSTANCE.render(combinedMatrix, renderParams.partialTicks);
}
}
}
@@ -363,154 +326,47 @@ public class LodRenderer
//================//
profiler.popPush("LOD cleanup");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderEventParam);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderParams);
lightmap.unbind();
this.quadIBO.unbind();
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
shaderProgram.unbind();
lodShaderProgram.unbind();
// end of internal LOD profiling
profiler.pop();
}
private void renderTransparentBuffersAndFireApiEvent(
IProfilerWrapper profiler,
DhApiRenderParam renderEventParam,
RenderBufferHandler renderBufferHandler)
{
profiler.popPush("LOD Transparent");
GLMC.enableBlend();
GLMC.enableDepthTest();
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam);
renderBufferHandler.renderTransparent(this, renderEventParam);
}
/** called by each {@link ColumnRenderBuffer} before rendering */
public void setModelViewMatrixOffset(DhBlockPos pos, DhApiRenderParam renderEventParam) throws IllegalStateException
{
Vec3d cam = MC_RENDER.getCameraExactPosition();
Vec3f modelPos = new Vec3f((float) (pos.getX() - cam.x), (float) (pos.getY() - cam.y), (float) (pos.getZ() - cam.z));
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
shaderProgram.bind();
shaderProgram.setModelOffsetPos(modelPos);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeBufferRenderEvent.class, new DhApiBeforeBufferRenderEvent.EventParam(renderEventParam, modelPos));
}
public void drawVbo(GLVertexBuffer vbo, ColumnRenderBuffer parentBufferContainer)
{
// this should only be enabled for debugging
if (Config.Client.Advanced.Debugging.OpenGl.validateBufferIdsBeforeRendering.get())
{
// this is a fairly slow call and enabling it will reduce FPS significantly
if (!GL32.glIsBuffer(vbo.getId()))
{
if (RATE_LIMITED_LOGGER.canLog()) // can log check to prevent creating a bunch of strings unnecessarily
{
RATE_LIMITED_LOGGER.warn("Attempted to draw invalid buffer: [" + vbo.getId() + "], expected size: ["+vbo.getSize()+"], upload complete: [" + parentBufferContainer.buffersUploaded + "], upload in progress: [" + parentBufferContainer.uploadInProgress() + "], buffer blockPos: ["+parentBufferContainer.minCornerBlockPos +"].");
}
return;
}
}
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount() / 4) * 6, // TODO what does the 4 and 6 here represent?
this.quadIBO.getType(), 0);
vbo.unbind();
}
//=================//
// Setup Functions //
//=================//
private void setupGLStateAndRenderObjects(
IProfilerWrapper profiler,
private void setGLState(
DhApiRenderParam renderEventParam,
boolean firstPass)
{
//===================//
// draw params setup //
// framebuffer setup //
//===================//
profiler.push("LOD draw setup");
if (!this.isSetupComplete)
{
this.setupRenderObjects();
// shouldn't normally happen, but just in case
if (!this.isSetupComplete)
{
return;
}
}
if (MC_RENDER.getTargetFrameBufferViewportWidth() != this.cachedWidth
|| MC_RENDER.getTargetFrameBufferViewportHeight() != this.cachedHeight)
{
// just resizing the textures doesn't work when Optifine is present,
// so recreate the textures with the new size instead
this.createColorAndDepthTextures();
}
IDhApiFramebuffer activeFrameBuffer = this.framebuffer;
// get the active framebuffer
IDhApiFramebuffer frameBuffer = this.framebuffer;
IDhApiFramebuffer framebufferOverride = OverrideInjector.INSTANCE.get(IDhApiFramebuffer.class);
if (framebufferOverride != null && framebufferOverride.overrideThisFrame())
{
activeFrameBuffer = framebufferOverride;
frameBuffer = framebufferOverride;
}
this.activeFramebufferId = frameBuffer.getId();
frameBuffer.bind();
this.activeFramebufferId = activeFrameBuffer.getId();
this.activeDepthTextureId = this.depthTexture.getTextureId();
if (this.nullableColorTexture != null)
{
this.activeColorTextureId = this.nullableColorTexture.getTextureId();
}
else
{
// get MC's color texture
int mcColorTextureId = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
this.activeColorTextureId = mcColorTextureId;
}
// Bind LOD frame buffer
activeFrameBuffer.bind();
//==========//
// bindings //
//==========//
// by default draw everything as triangles
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling();
@@ -527,21 +383,16 @@ public class LodRenderer
// This is required for MC versions 1.21.5+
// due to MC updating the lightmap by changing the viewport size
GL32.glViewport(0, 0, this.cachedWidth, this.cachedHeight);
GL32.glViewport(0, 0, this.textureWidth, this.textureHeight);
/*---------Bind required objects--------*/
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
// also binds LightmapTexture, VAO, and ShaderProgram
if (!this.isSetupComplete)
{
this.setupRenderObjects();
}
else
{
this.lodRenderProgram.bind();
}
this.lodRenderProgram.bind();
//==========//
// uniforms //
//==========//
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null)
{
@@ -551,6 +402,37 @@ public class LodRenderer
this.lodRenderProgram.fillUniformData(renderEventParam);
//===============//
// texture setup //
//===============//
// resize the textures if needed
if (MC_RENDER.getTargetFrameBufferViewportWidth() != this.textureWidth
|| MC_RENDER.getTargetFrameBufferViewportHeight() != this.textureHeight)
{
// just resizing the textures doesn't work when Optifine is present,
// so recreate the textures with the new size instead
this.createAndBindTextures();
}
// set the active textures
this.activeDepthTextureId = this.depthTexture.getTextureId();
if (this.nullableColorTexture != null)
{
this.activeColorTextureId = this.nullableColorTexture.getTextureId();
}
else
{
// get MC's color texture
int mcColorTextureId = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
this.activeColorTextureId = mcColorTextureId;
}
// needs to be fired after all the textures have been created/bound
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderEventParam);
if (clearTextures)
@@ -565,7 +447,7 @@ public class LodRenderer
{
// Due to using MC/Optifine's framebuffer we need to re-bind the depth texture,
// otherwise we'll be writing to MC/Optifine's depth texture which causes rendering issues
activeFrameBuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
frameBuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
// don't clear the color texture, that removes the sky
@@ -576,92 +458,88 @@ public class LodRenderer
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
}
}
}
/** Setup all render objects - MUST be called on the render thread */
private void setupRenderObjects()
private boolean createRenderObjects()
{
if (this.isSetupComplete)
if (this.renderObjectsCreated)
{
LOGGER.warn("Renderer setup called but it has already completed setup!");
return;
return false;
}
if (!GLProxy.hasInstance())
{
// shouldn't normally happen, but just in case
LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
return;
return false;
}
try
LOGGER.info("Setting up renderer");
this.lodRenderProgram = new DhTerrainShaderProgram();
this.quadIBO = new QuadElementBuffer();
this.quadIBO.reserve(LodBufferContainer.MAX_QUADS_PER_BUFFER);
// create or get the frame buffer
if (AbstractOptifineAccessor.optifinePresent())
{
this.setupLock.lock();
LOGGER.info("Setting up renderer");
this.lodRenderProgram = new DhTerrainShaderProgram();
this.quadIBO = new QuadElementBuffer();
this.quadIBO.reserve(ColumnRenderBuffer.MAX_QUADS_PER_BUFFER);
// create or get the frame buffer
if (AbstractOptifineAccessor.optifinePresent())
{
// use MC/Optifine's default FrameBuffer so shaders won't remove the LODs
int currentFrameBufferId = MC_RENDER.getTargetFrameBuffer();
this.framebuffer = new DhFramebuffer(currentFrameBufferId);
this.usingMcFrameBuffer = true;
}
else
{
// normal use case
this.framebuffer = new DhFramebuffer();
this.usingMcFrameBuffer = false;
}
// create and bind the necessary textures
this.createColorAndDepthTextures();
if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE)
{
// This generally means something wasn't bound, IE missing either the color or depth texture
LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
return;
}
this.isSetupComplete = true;
LOGGER.info("Renderer setup complete");
// use MC/Optifine's default FrameBuffer so shaders won't remove the LODs
int currentFrameBufferId = MC_RENDER.getTargetFrameBuffer();
this.framebuffer = new DhFramebuffer(currentFrameBufferId);
this.usingMcFrameBuffer = true;
}
finally
else
{
this.setupLock.unlock();
// normal use case
this.framebuffer = new DhFramebuffer();
this.usingMcFrameBuffer = false;
}
// create and bind the necessary textures
this.createAndBindTextures();
if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE)
{
// This generally means something wasn't bound, IE missing either the color or depth texture
LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
return false;
}
LOGGER.info("Renderer setup complete");
return true;
}
/** also binds the new textures to the {@link LodRenderer#framebuffer} */
private void createColorAndDepthTextures()
private void createAndBindTextures()
{
int oldWidth = this.cachedWidth;
int oldHeight = this.cachedHeight;
this.cachedWidth = MC_RENDER.getTargetFrameBufferViewportWidth();
this.cachedHeight = MC_RENDER.getTargetFrameBufferViewportHeight();
int oldWidth = this.textureWidth;
int oldHeight = this.textureHeight;
this.textureWidth = MC_RENDER.getTargetFrameBufferViewportWidth();
this.textureHeight = MC_RENDER.getTargetFrameBufferViewportHeight();
DhApiTextureCreatedParam textureCreatedParam = new DhApiTextureCreatedParam(
oldWidth, oldHeight,
this.cachedWidth, this.cachedHeight
this.textureWidth, this.textureHeight
);
// DhApiColorDepthTextureCreatedEvent needs to be kept around since old versions of Iris need it
ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class, new DhApiColorDepthTextureCreatedEvent.EventParam(textureCreatedParam));
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeColorDepthTextureCreatedEvent.class, textureCreatedParam);
// also update the override if present
// also update the framebuffer override if present
IDhApiFramebuffer framebufferOverride = OverrideInjector.INSTANCE.get(IDhApiFramebuffer.class);
this.depthTexture = new DHDepthTexture(this.cachedWidth, this.cachedHeight, EDhDepthBufferFormat.DEPTH32F);
this.depthTexture = new DHDepthTexture(this.textureWidth, this.textureHeight, EDhDepthBufferFormat.DEPTH32F);
this.framebuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
if (framebufferOverride != null)
{
@@ -671,7 +549,7 @@ public class LodRenderer
// if we are using MC's frame buffer, a color texture is already present and shouldn't need to be bound
if (!this.usingMcFrameBuffer)
{
this.nullableColorTexture = DhColorTexture.builder().setDimensions(this.cachedWidth, this.cachedHeight)
this.nullableColorTexture = DhColorTexture.builder().setDimensions(this.textureWidth, this.textureHeight)
.setInternalFormat(EDhInternalTextureFormat.RGBA8)
.setPixelType(EDhPixelType.UNSIGNED_BYTE)
.setPixelFormat(EDhPixelFormat.RGBA)
@@ -694,20 +572,129 @@ public class LodRenderer
//===============//
// LOD rendering //
//===============//
private void renderLodPass(IDhApiShaderProgram shaderProgram, RenderBufferHandler lodBufferHandler, RenderParams renderEventParam, boolean opaquePass)
{
//=======================//
// debug wireframe setup //
//=======================//
// TODO move this logic into LodRenderer so all the GL states can be handled there
boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
if (renderWireframe)
{
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
GLMC.disableFaceCulling();
}
else
{
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling();
}
if (!opaquePass)
{
GLMC.enableBlend();
GLMC.enableDepthTest();
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
}
else
{
GLMC.disableBlend();
}
//===========//
// rendering //
//===========//
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam);
SortedArraySet<LodBufferContainer> lodBufferContainer = lodBufferHandler.getColumnRenderBuffers();
if (lodBufferContainer != null)
{
for (int lodIndex = 0; lodIndex < lodBufferContainer.size(); lodIndex++)
{
LodBufferContainer bufferContainer = lodBufferContainer.get(lodIndex);
this.setShaderProgramMvmOffset(bufferContainer.minCornerBlockPos, shaderProgram, renderEventParam);
GLVertexBuffer[] vbos = opaquePass ? bufferContainer.vbos : bufferContainer.vbosTransparent;
for (int vboIndex = 0; vboIndex < vbos.length; vboIndex++)
{
GLVertexBuffer vbo = vbos[vboIndex];
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
GL32.glDrawElements(
GL32.GL_TRIANGLES,
(vbo.getVertexCount() / 4) * 6, // TODO what does the 4 and 6 here represent?
this.quadIBO.getType(), 0);
vbo.unbind();
}
}
}
//=========================//
// debug wireframe cleanup //
//=========================//
if (renderWireframe)
{
// default back to GL_FILL since all other rendering uses it
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling();
}
}
/**
* the MVM offset is needed so LODs can be rendered anywhere in the MC world
* without running into floating point percision loss.
*/
private void setShaderProgramMvmOffset(DhBlockPos pos, IDhApiShaderProgram shaderProgram, RenderParams renderEventParam) throws IllegalStateException
{
Vec3d camPos = renderEventParam.exactCameraPosition;
Vec3f modelPos = new Vec3f(
(float) (pos.getX() - camPos.x),
(float) (pos.getY() - camPos.y),
(float) (pos.getZ() - camPos.z));
shaderProgram.bind();
shaderProgram.setModelOffsetPos(modelPos);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeBufferRenderEvent.class, new DhApiBeforeBufferRenderEvent.EventParam(renderEventParam, modelPos));
}
//===============//
// API functions //
//===============//
/** Returns -1 if no frame buffer has been bound yet */
/** @return -1 if no frame buffer has been bound yet */
public int getActiveFramebufferId() { return this.activeFramebufferId; }
/** Returns -1 if no texture has been bound yet */
/** @return -1 if no texture has been bound yet */
public int getActiveColorTextureId() { return this.activeColorTextureId; }
/**
* FIXME it's possible for this to return an invalid texture ID if the renderer is being re-built at the same time
* Returns -1 if no texture has been bound yet
*/
/** @return -1 if no texture has been bound yet */
public int getActiveDepthTextureId() { return this.activeDepthTextureId; }
@@ -0,0 +1,157 @@
package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
/**
* An extension of {@link DhApiRenderParam}
* that allows additional validation and putting all
* rendering variables in a single place.
*/
public class RenderParams extends DhApiRenderParam
{
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public IDhClientWorld dhClientWorld;
public IDhClientLevel dhClientLevel;
public IClientLevelWrapper clientLevelWrapper;
public ILightMapWrapper lightmap;
public RenderBufferHandler renderBufferHandler;
public GenericObjectRenderer genericRenderer;
public Vec3d exactCameraPosition;
public boolean validationRun = false;
//=============//
// constructor //
//=============//
public RenderParams(
EDhApiRenderPass renderPass,
float newPartialTicks,
Mat4f newMcProjectionMatrix, Mat4f newMcModelViewMatrix,
IClientLevelWrapper clientLevelWrapper
)
{
super(renderPass,
newPartialTicks,
RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
newMcProjectionMatrix, newMcModelViewMatrix,
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
clientLevelWrapper.getMinHeight());
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
if (this.dhClientWorld != null)
{
// TODO changing to getOrLoadClientLevel() fixes Immersive Portals only rendering the level the user starts in
// however this may break how other level handling is done so James doesn't want to change it.
// Special handling may be necessary when Immersive Portals is present, although additional testing is needed.
this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper);
if (this.dhClientLevel != null)
{
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
this.genericRenderer = this.dhClientLevel.getGenericRenderer();
}
}
this.clientLevelWrapper = clientLevelWrapper;
this.lightmap = MC_RENDER.getLightmapWrapper(this.clientLevelWrapper);
if (MC_CLIENT.playerExists())
{
this.exactCameraPosition = MC_RENDER.getCameraExactPosition();
}
}
//======================//
// parameter validation //
//======================//
/**
* Should be called before rendering is done.
* @return a message if LODs shouldn't be rendered, null if the LODs can render
*/
public String getValidationErrorMessage()
{
// Note: all strings here should be constants to prevent String allocations
this.validationRun = true;
if (!MC_CLIENT.playerExists())
{
return "No Player Exists";
}
if (this.dhClientWorld == null)
{
return "No DH Client World Loaded";
}
if (this.dhClientLevel == null)
{
return "No DH Client Level Loaded";
}
if (this.clientLevelWrapper == null)
{
return "No Client Level Wrapper Loaded";
}
if (this.lightmap == null)
{
return "No Lightmap Loaded";
}
if (this.renderBufferHandler == null)
{
return "No RenderBufferHandler Present";
}
if (this.genericRenderer == null)
{
return "No Generic Renderer Present";
}
if (this.dhModelViewMatrix == null
|| this.mcModelViewMatrix == null)
{
return "No MVM or Proj Matrix Given";
}
if (AbstractOptifineAccessor.optifinePresent()
&& MC_RENDER.getTargetFrameBuffer() == -1)
{
// wait for MC to finish setting up their renderer
return "Optifine Target Frame Buffer not set";
}
return null;
}
}
@@ -34,10 +34,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
/**
* This holds miscellaneous helper code
* to be used in the rendering process.
*
* @author James Seibel
* @version 2022-8-21
* used in the rendering process.
*/
public class RenderUtil
{
@@ -86,6 +83,12 @@ public class RenderUtil
return mcModelViewMat.copy();
}
//=============//
// clip planes //
//=============//
public static float getNearClipPlaneDistanceInBlocks(float partialTicks)
{
// 0.2 should provide a decent distance so the clip plane isn't visible
@@ -211,46 +214,6 @@ public class RenderUtil
return -1.0f;
}
/** @return a message if LODs shouldn't be rendered, null if the LODs can render */
public static String shouldLodsRender(ILevelWrapper levelWrapper, DhApiRenderParam renderEventParam)
{
if (!MC.playerExists())
{
return "No Player Exists";
}
if (levelWrapper == null)
{
return "No Level Given";
}
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
if (clientWorld == null)
{
return "No Client World Loaded";
}
// TODO changing to getOrLoadClientLevel() fixes Immersive Portals only rendering the level the user starts in
// however this may break how other level handling is done so James doesn't want to change it.
// Special handling may be necessary when Immersive Portals is present, although additional testing is needed.
IDhClientLevel level = clientWorld.getClientLevel(levelWrapper);
if (level == null)
{
return "No Client Level Loaded"; //Level is not ready yet.
}
if (MC_RENDER.getLightmapWrapper(levelWrapper) == null)
{
return "No Lightmap loaded";
}
if (renderEventParam.dhModelViewMatrix == null
|| renderEventParam.mcModelViewMatrix == null)
{
return "No MVM or Proj Matrix Given";
}
return null;
}
}
@@ -21,188 +21,70 @@ package com.seibel.distanthorizons.core.util.objects;
import java.util.*;
public class SortedArraySet<E> implements SortedSet<E>
public class SortedArraySet<E>
{
private final ArrayList<E> list;
private final ArrayList<E> list = new ArrayList<>();
private final HashSet<E> set = new HashSet<>();
private final Comparator<? super E> comparator;
public SortedArraySet()
{
list = new ArrayList<>();
comparator = null;
}
//==============//
// constructors //
//==============//
public SortedArraySet(Comparator<? super E> comparator)
{
list = new ArrayList<>();
this.comparator = comparator;
}
public SortedArraySet(Collection<? extends E> collection)
{
list = new ArrayList<>(collection);
comparator = null;
list.sort(null);
}
public SortedArraySet(Collection<? extends E> collection, Comparator<? super E> comparator)
{
list = new ArrayList<>(collection);
this.comparator = comparator;
list.sort(comparator);
}
@Override
public Comparator<? super E> comparator()
{
return comparator;
}
@Override
public E first()
{
return list.get(0);
}
@Override
public E last()
{
return list.get(list.size() - 1);
}
@Override
public int size()
{
return list.size();
}
@Override
public boolean isEmpty()
{
return list.isEmpty();
}
@Override
public boolean contains(Object o)
{
return list.contains(o);
}
@Override
public Iterator<E> iterator()
{
return list.iterator();
}
public ListIterator<E> listIterator()
{
return list.listIterator();
}
public ListIterator<E> listIterator(int index)
{
return list.listIterator(index);
this.list.addAll(collection);
this.list.sort(comparator);
}
@Override
public Object[] toArray()
{
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a)
{
return list.toArray(a);
}
//==============//
// list methods //
//==============//
@Override
public boolean add(E e)
public void add(E element)
{
int index = Collections.binarySearch(list, e, comparator);
if (index < 0)
if (!this.set.contains(element))
{
index = ~index;
this.list.add(element);
}
list.add(index, e);
return true;
}
@Override
public boolean remove(Object o)
{
return list.remove(o);
}
public E get(int index) { return this.list.get(index); }
@Override
public boolean containsAll(Collection<?> c)
{
return list.containsAll(c);
}
public int size() { return this.list.size(); }
@Override
public boolean addAll(Collection<? extends E> c)
{
boolean changed = false;
for (E e : c)
{
changed |= add(e);
}
return changed;
}
@Override
public boolean retainAll(Collection<?> c)
{
return list.retainAll(c);
}
@Override
public boolean removeAll(Collection<?> c)
{
return list.removeAll(c);
}
@Override
public void clear()
{
list.clear();
this.list.clear();
this.set.clear();
}
//================//
// base overrides //
//================//
@Override
public String toString()
{
return "SortedArraySet{" +
"list=" + list +
", comparator=" + comparator +
"list=" + this.list +
", comparator=" + this.comparator +
'}';
}
@Override
public SortedSet<E> subSet(E fromElement, E toElement)
{
int fromIndex = Collections.binarySearch(list, fromElement, comparator);
if (fromIndex < 0) fromIndex = ~fromIndex;
int toIndex = Collections.binarySearch(list, toElement, comparator);
if (toIndex < 0) toIndex = ~toIndex;
return new SortedArraySet<>(list.subList(fromIndex, toIndex), comparator);
}
@Override
public SortedSet<E> headSet(E toElement)
{
int toIndex = Collections.binarySearch(list, toElement, comparator);
if (toIndex < 0) toIndex = ~toIndex;
return new SortedArraySet<>(list.subList(0, toIndex), comparator);
}
@Override
public SortedSet<E> tailSet(E fromElement)
{
int fromIndex = Collections.binarySearch(list, fromElement, comparator);
if (fromIndex < 0) fromIndex = ~fromIndex;
return new SortedArraySet<>(list.subList(fromIndex, list.size()), comparator);
}
}
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindab
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -79,7 +80,7 @@ public interface IMinecraftRenderWrapper extends IBindable
/** Can return null if the given level hasn't had a light map assigned to it */
@Nullable
ILightMapWrapper getLightmapWrapper(ILevelWrapper level);
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
}