Working on connecting the dots with a7 stuff

This commit is contained in:
TomTheFurry
2022-05-26 12:38:56 +08:00
parent 510df79f90
commit f35cf673e1
15 changed files with 1276 additions and 32 deletions
@@ -19,12 +19,9 @@
package com.seibel.lod.core.api.internal;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.LodWorld;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This stores objects and variables that
@@ -0,0 +1,318 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.api.internal.a7;
import com.seibel.lod.core.Config;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.RendererType;
import com.seibel.lod.core.handlers.LodDimensionFinder;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.objects.a7.DHLevel;
import com.seibel.lod.core.objects.a7.DHWorld;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.RenderSystemTest;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
* This holds the methods that should be called
* by the host mod loader (Fabric, Forge, etc.).
* Specifically for the client.
*
* @author James Seibel
* @version 2022-4-27
*/
public class ClientApi
{
public static final Logger LOGGER = LogManager.getLogger(ClientApi.class.getSimpleName());
public static boolean prefLoggerEnabled = false;
public static final ClientApi INSTANCE = new ClientApi();
public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory();
public static LodRenderer renderer = new LodRenderer(lodBufferBuilderFactory);
public static RenderSystemTest testRenderer = new RenderSystemTest();
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final ServerApi EVENT_API = ServerApi.INSTANCE;
public static final boolean ENABLE_LAG_SPIKE_LOGGING = false;
public static final long LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS);
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
public static LodDimensionFinder DIMENSION_FINDER = new LodDimensionFinder();;
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
public void end(String source) {
if (!ENABLE_LAG_SPIKE_LOGGING) return;
timer = System.nanoTime() - timer;
if (timer > LAG_SPIKE_THRESHOLD_NS) {
LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!");
}
}
}
/**
* there is some setup that should only happen once,
* once this is true that setup has completed
*/
private boolean firstTimeSetupComplete = false;
private boolean configOverrideReminderPrinted = false;
public boolean rendererDisabledBecauseOfExceptions = false;
private ClientApi()
{
}
public static void logToChat(Level logLevel, String str) {
String prefix = "["+ModInfo.READABLE_NAME+"] ";
if (logLevel == Level.ERROR) {
prefix += "\u00A74";
} else if (logLevel == Level.WARN) {
prefix += "\u00A76";
} else if (logLevel == Level.INFO) {
prefix += "\u00A7f";
} else if (logLevel == Level.DEBUG) {
prefix += "\u00A77";
} else if (logLevel == Level.TRACE) {
prefix += "\u00A78";
} else {
prefix += "\u00A7f";
}
prefix += "\u00A7l\u00A7u";
prefix += logLevel.name();
prefix += ":\u00A7r ";
if (MC != null) MC.sendChatMessage(prefix + str);
}
public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world)
{
//TODO: Implement
}
public void clientChunkSaveEvent(IChunkWrapper chunk, IWorldWrapper world)
{
//TODO: Implement
}
public void clientLevelUnloadEvent(IWorldWrapper world)
{
if (SharedApi.currentServer != null) return;
if (SharedApi.currentWorld != null) {
SharedApi.currentWorld.unloadLevel(world);
}
}
public void clientLevelLoadEvent(IWorldWrapper world)
{
if (SharedApi.currentServer != null) return;
if (SharedApi.currentWorld != null) {
SharedApi.currentWorld.getOrLoadLevel(world);
}
}
private long lastFlush = 0;
public void preRender() {
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-PreRender");
boolean doFlush = System.nanoTime() - lastFlush >= SPAM_LOGGER_FLUSH_NS;
if (doFlush) {
lastFlush = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
// only run the first time setup once
if (!firstTimeSetupComplete) firstFrameSetup();
if (ModInfo.IS_DEV_BUILD)
{
// config overrides should only be used in the developer builds
applyDeveloperConfigOverrides();
}
if (SharedApi.currentServer == null && SharedApi.currentWorld != null) {
// In single player.
SharedApi.currentWorld.asyncTick();
}
//FIXME: Is it always 'terrain' that is the previous thing in the profiler?
profiler.push("terrain"); // go back into "terrain"
}
public void renderLods(IWorldWrapper world, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-RenderLevel");
try {
if (!MC.playerExists()) return;
if (world == null) return;
DHWorld dhWorld = SharedApi.currentWorld;
if (dhWorld == null) return;
DHLevel level = (SharedApi.currentServer == null) ? dhWorld.getOrLoadLevel(world) : dhWorld.getLevel(world);
if (level == null) return;
if (prefLoggerEnabled) {
level.dumpRamUsage();
}
if (SharedApi.currentServer == null) {
// In multiplayer, without access to the server-side stuff. So we need to do some extra work.
level.asyncTick();
}
if (Config.Client.Advanced.Debugging.rendererType.get() == RendererType.DEFAULT) {
if (MC_RENDER.playerHasBlindnessEffect()) {
// if the player is blind, don't render LODs,
// and don't change minecraft's fog
// which blindness relies on.
return;
}
if (MC_RENDER.getLightmapWrapper() == null)
return;
profiler.push("Render-Lods");
if (!rendererDisabledBecauseOfExceptions) {
try {
level.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
} catch (RuntimeException e) {
rendererDisabledBecauseOfExceptions = true;
LOGGER.error("Renderer thrown an uncaught exception: ", e);
try {
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
+ " renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
MC.sendChatMessage("\u00A74Exception detail: " + e.toString());
} catch (RuntimeException ignored) {
}
}
}
profiler.pop(); // "Render-Lods"
} else if (Config.Client.Advanced.Debugging.rendererType.get() == RendererType.DEBUG) {
profiler.push("Render-Test");
try {
ClientApi.testRenderer.render();
} catch (RuntimeException e) {
LOGGER.error("Renderer thrown an uncaught exception: ", e);
try {
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
+ " renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
MC.sendChatMessage("\u00A74Exception detail: " + e.toString());
} catch (RuntimeException ignored) {
}
}
profiler.pop(); // end LODTestRendering
}
}
catch (Exception e)
{
LOGGER.error("client level rendering uncaught exception: ", e);
} finally {
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
}
}
/** used in a development environment to change settings on the fly */
private void applyDeveloperConfigOverrides()
{
// remind the user that the config override is active
if (!configOverrideReminderPrinted)
{
MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION);
MC.sendChatMessage("You are running an unsupported version of the mod!");
MC.sendChatMessage("Here be dragons!");
configOverrideReminderPrinted = true;
}
}
//=================//
// DEBUG USE //
//=================//
// Trigger once on key press, with CLIENT PLAYER.
public void keyPressedEvent(int glfwKey)
{
if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
return;
if (glfwKey == GLFW.GLFW_KEY_F8)
{
Config.Client.Advanced.Debugging.debugMode.set(DebugMode.next(Config.Client.Advanced.Debugging.debugMode.get()));
MC.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugMode.get());
}
if (glfwKey == GLFW.GLFW_KEY_F6)
{
Config.Client.Advanced.Debugging.rendererType.set(RendererType.next(Config.Client.Advanced.Debugging.rendererType.get()));
MC.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererType.get());
}
if (glfwKey == GLFW.GLFW_KEY_P)
{
prefLoggerEnabled = !prefLoggerEnabled;
MC.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
}
}
//=================//
// Lod maintenance //
//=================//
// FIXME: I need a onLastFrameCleanup() callback in Render Thread... Which calls renderer.cleanup()
/** This event is called once during the first frame Minecraft renders in the world. */
public void firstFrameSetup()
{
// make sure the GLProxy is created before the LodBufferBuilder needs it
GLProxy.getInstance();
firstTimeSetupComplete = true;
}
}
@@ -0,0 +1,107 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.api.internal.a7;
import com.seibel.lod.core.api.internal.InternalApiShared;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.BatchGenerator;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.objects.DHChunkPos;
import com.seibel.lod.core.objects.DHRegionPos;
import com.seibel.lod.core.objects.a7.DHWorld;
import com.seibel.lod.core.objects.a7.Server;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
/**
* This holds the methods that should be called by the host mod loader (Fabric,
* Forge, etc.). Specifically server and client events.
*
* @author James Seibel
* @version 2021-11-12
*/
public class ServerApi
{
public static final boolean ENABLE_STACK_DUMP_LOGGING = false;
public static final ServerApi INSTANCE = new ServerApi();
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
private boolean isCurrentlyOnSinglePlayerServer = false;
private ServerApi()
{
}
// =============//
// tick events //
// =============//
private int lastWorldGenTickDelta = 0;
public void serverTickEvent()
{
lastWorldGenTickDelta--;
if (SharedApi.currentWorld != null && lastWorldGenTickDelta <= 0) {
lastWorldGenTickDelta = 20;
DHWorld dhWorld = SharedApi.currentWorld;
dhWorld.tick();
}
}
public void serverWorldLoadEvent() {
Server server = new Server();
SharedApi.currentServer = server;
SharedApi.currentWorld = new DHWorld();
}
public void serverWorldUnloadEvent() {
SharedApi.currentWorld.close();
SharedApi.currentWorld = null;
SharedApi.currentServer = null;
}
public void serverLevelLoadEvent(IWorldWrapper world) {
SharedApi.currentWorld.getOrLoadLevel(world);
}
public void serverLevelUnloadEvent(IWorldWrapper world) {
SharedApi.currentWorld.unloadLevel(world);
}
public void serverSaveEvent() {
SharedApi.currentWorld.save();
}
public void chunkSaveEvent(IChunkWrapper chunk, IWorldWrapper world) {
//TODO
}
}
@@ -0,0 +1,51 @@
package com.seibel.lod.core.api.internal.a7;
import com.seibel.lod.core.objects.a7.DHWorld;
import com.seibel.lod.core.objects.a7.Server;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public class SharedApi {
public static DHWorld currentWorld;
public static Server currentServer;
public static IMinecraftSharedWrapper MC;
public static IMinecraftClientWrapper MC_CLIENT;
public static void onServerStart() {
if (MC.isServerJar()) {
ServerApi.INSTANCE.serverWorldLoadEvent();
} else if (MC_CLIENT.hasSinglePlayerServer()) {
ServerApi.INSTANCE.serverWorldLoadEvent();
} // else do nothing
}
public static void onServerStop() {
if (MC.isServerJar()) {
ServerApi.INSTANCE.serverWorldUnloadEvent();
} else if (MC_CLIENT.hasSinglePlayerServer()) {
ServerApi.INSTANCE.serverWorldUnloadEvent();
} // else do nothing
}
public static void onLevelLoad(IWorldWrapper world) {
if (MC.isServerJar()) {
ServerApi.INSTANCE.serverLevelLoadEvent(world);
} else if (MC_CLIENT.hasSinglePlayerServer()) {
ServerApi.INSTANCE.serverLevelLoadEvent(world);
} else {
ClientApi.INSTANCE.clientLevelLoadEvent(world);
}
}
public static void onLevelUnload(IWorldWrapper world) {
if (MC.isServerJar()) {
ServerApi.INSTANCE.serverLevelUnloadEvent(world);
} else if (MC_CLIENT.hasSinglePlayerServer()) {
ServerApi.INSTANCE.serverLevelUnloadEvent(world);
} else {
ClientApi.INSTANCE.clientLevelUnloadEvent(world);
}
}
}
@@ -51,24 +51,37 @@ public enum DebugMode
/** Only draw overlapping LOD quads, and draws in wireframe. */
SHOW_OVERLAPPING_QUADS_WIREFRAME;
/** used when cycling through the different modes */
private DebugMode next;
static
{
OFF.next = SHOW_WIREFRAME;
SHOW_WIREFRAME.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE;
SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME;
SHOW_GENMODE_WIREFRAME.next = SHOW_OVERLAPPING_QUADS;
SHOW_OVERLAPPING_QUADS.next = SHOW_OVERLAPPING_QUADS_WIREFRAME;
SHOW_OVERLAPPING_QUADS_WIREFRAME.next = OFF;
}
/** returns the next debug mode */
// Deprecated: use DebugMode.next() instead
@Deprecated
public DebugMode getNext()
{
return this.next;
return next(this);
}
public static DebugMode next(DebugMode type) {
switch (type) {
case OFF: return SHOW_WIREFRAME;
case SHOW_WIREFRAME: return SHOW_DETAIL;
case SHOW_DETAIL: return SHOW_DETAIL_WIREFRAME;
case SHOW_DETAIL_WIREFRAME: return SHOW_GENMODE;
case SHOW_GENMODE: return SHOW_GENMODE_WIREFRAME;
case SHOW_GENMODE_WIREFRAME: return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS: return SHOW_OVERLAPPING_QUADS_WIREFRAME;
default: return OFF;
}
}
public static DebugMode previous(DebugMode type) {
switch (type) {
case OFF: return SHOW_OVERLAPPING_QUADS_WIREFRAME;
case SHOW_OVERLAPPING_QUADS_WIREFRAME: return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS: return SHOW_GENMODE_WIREFRAME;
case SHOW_GENMODE_WIREFRAME: return SHOW_GENMODE;
case SHOW_GENMODE: return SHOW_DETAIL_WIREFRAME;
case SHOW_DETAIL_WIREFRAME: return SHOW_DETAIL;
case SHOW_DETAIL: return SHOW_WIREFRAME;
default: return OFF;
}
}
}
@@ -1,20 +1,27 @@
package com.seibel.lod.core.objects.a7;
import com.seibel.lod.core.api.internal.InternalApiShared;
import com.seibel.lod.core.api.internal.a7.ClientApi;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.a7.data.DataFileHandler;
import com.seibel.lod.core.objects.a7.pos.DhBlockPos2D;
import com.seibel.lod.core.objects.a7.render.RenderBufferHandler;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.a7LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.EventLoop;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import java.io.Closeable;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
public class DHLevel extends LodQuadTree {
public class DHLevel extends LodQuadTree implements Closeable {
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
public final File saveFolder; // Could be null, for no saving
@@ -23,11 +30,16 @@ public class DHLevel extends LodQuadTree {
public final ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHLevelTickerThread", 2);
private final AtomicBoolean isRunning = new AtomicBoolean(false);
public final IWorldWrapper level;
public a7LodRenderer renderer;
public final DHWorld world;
public DHLevel(File saveFolder, IWorldWrapper level) {
public EventLoop eventLoop;
public DHLevel(DHWorld world, File saveFolder, IWorldWrapper level) {
super(CONFIG.client().graphics().quality().getLodChunkRenderDistance()*16,
MC.getPlayerBlockPos().x,
MC.getPlayerBlockPos().z);
this.world = world;
this.saveFolder = saveFolder;
if (saveFolder != null) {
dataFileHandler = new DataFileHandler(saveFolder, this);
@@ -36,6 +48,7 @@ public class DHLevel extends LodQuadTree {
}
renderBufferHandler = new RenderBufferHandler(this);
this.level = level;
eventLoop = new EventLoop(world.dhTickerThread, this::tick);
}
// Should be called by server tick thread, or called by render thread but only 20 times per second, or less?
@@ -64,11 +77,60 @@ public class DHLevel extends LodQuadTree {
return dataFileHandler;
}
public void render(LodRenderProgram renderContext) {
renderBufferHandler.render(renderContext);
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) {
if (renderer == null) {
renderer = new a7LodRenderer(this);
}
renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
}
public int getMinY() {
return level.getMinHeight();
}
public void dumpRamUsage() {
//TODO
}
public void asyncTick() {
eventLoop.tick();
}
public void close() {
eventLoop.halt();
if (dataFileHandler != null) {
dataFileHandler.close();
}
}
public void saveFlush() {
if (dataFileHandler != null) {
dataFileHandler.save();
}
}
public void viewDistanceChangedEvent()
{
// calculate how wide the dimension(s) should be in regions
int chunksWide;
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(),
LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1;
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
// make sure we have an odd number of regions
newWidth += (newWidth & 1) == 0 ? 1 : 0;
// do the dimensions need to change in size?
if (InternalApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
// update the dimensions to fit the new width
InternalApiShared.lodWorld.resizeDimensionRegionWidth(newWidth);
InternalApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth;
ClientApi.renderer.setupBuffers();
recalculateWidths = false;
// LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: "
// + newWidth );
}
DetailDistanceUtil.updateSettings();
}
}
@@ -1,25 +1,36 @@
package com.seibel.lod.core.objects.a7;
import com.seibel.lod.core.handlers.LodDimensionFinder;
import com.seibel.lod.core.Config;
import com.seibel.lod.core.api.internal.InternalApiShared;
import com.seibel.lod.core.api.internal.a7.ClientApi;
import com.seibel.lod.core.objects.a7.io.DHFolderHandler;
import com.seibel.lod.core.objects.a7.io.LevelToFileMatcher;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.EventLoop;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import java.io.Closeable;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
public class DHWorld {
public class DHWorld implements Closeable {
private final File saveDir;
private final HashMap<IWorldWrapper, DHLevel> levels;
private LevelToFileMatcher levelToFileMatcher = null;
public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2);
public EventLoop eventLoop = new EventLoop(dhTickerThread, this::tick);
public DHWorld() {
saveDir = DHFolderHandler.getCurrentWorldFolder();
levels = new HashMap<>();
}
public DHLevel getLevel(IWorldWrapper wrapper) {
public DHLevel getOrLoadLevel(IWorldWrapper wrapper) {
if (!levels.containsKey(wrapper)) {
if (levelToFileMatcher == null || levelToFileMatcher.getTargetWorld() != wrapper) {
levelToFileMatcher = new LevelToFileMatcher(saveDir, wrapper);
@@ -34,4 +45,48 @@ public class DHWorld {
}
} else return levels.get(wrapper);
}
public DHLevel getLevel(IWorldWrapper wrapper) {
return levels.get(wrapper);
}
public void unloadLevel(IWorldWrapper wrapper) {
if (levels.containsKey(wrapper)) {
levels.get(wrapper).close();
levels.remove(wrapper);
}
}
public void tick() {
int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16;
Iterator<DHLevel> iterator = levels.values().iterator();
while (iterator.hasNext()) {
DHLevel level = iterator.next();
if (level.viewDistance != newViewDistance) {
level.close();
iterator.remove();
}
}
DetailDistanceUtil.updateSettings();
}
public void doWorldGen() {
}
public void asyncTick() {
eventLoop.tick();
}
public void save() {
for (DHLevel level : levels.values()) {
level.saveFlush();
}
}
@Override
public void close() {
eventLoop.halt();
for (DHLevel level : levels.values()) {
level.close();
}
levels.clear();
}
}
@@ -97,6 +97,7 @@ public abstract class LodQuadTree {
}
final SectionDetailLayer[] sectionDetailLayers;
public final int viewDistance;
/**
* Constructor of the quadTree
@@ -106,6 +107,7 @@ public abstract class LodQuadTree {
*/
public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) {
assertContainerTypeConfigCorrect();
this.viewDistance = viewDistance;
{ // Calculate the max section detail
byte maxDetailLevel = getMaxDetailInRange(viewDistance * Math.sqrt(2));
@@ -107,7 +107,17 @@ public class DataFile {
return path.exists() && path.isFile() && path.canRead() && path.canWrite();
}
public void save(DHLevel level) throws IOException {
public void saveIfNeeded(DHLevel level, boolean freeMemory) {
if (loadedData == null) return;
if (!verifyPath()) return;
try {
save(level, freeMemory);
} catch (IOException e) {
//FIXME: Log and review this handling
}
}
public void save(DHLevel level, boolean freeMemory) throws IOException {
if (loadedData == null) throw new IllegalStateException("No data loaded");
if (!verifyPath()) throw new IOException("File path became invalid");
DataSourceSaver saver;
@@ -149,7 +159,15 @@ public class DataFile {
dataLevel = newDataLevel;
loader = saver;
if (freeMemory) {
loadedData = null;
}
}
public void close(DHLevel level) {
if (loadedData != null) {
saveIfNeeded(level, true);
}
}
}
@@ -9,15 +9,17 @@ import com.seibel.lod.core.objects.a7.render.RenderDataSource;
import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader;
import com.seibel.lod.core.util.LodUtil;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.rmi.server.ExportException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class DataFileHandler implements RenderDataProvider {
public class DataFileHandler implements RenderDataProvider, Closeable {
public static final List<OldFileConverter> CONVERTERS = new ArrayList<>();
public static final String FILE_EXTENSION = ".lod";
@@ -122,7 +124,7 @@ public class DataFileHandler implements RenderDataProvider {
DataFile dataFile = new DataFile(newFile, saver, dataSource);
dataFiles.put(pos, dataFile);
try {
dataFile.save(level);
dataFile.save(level, false);
} catch (Exception e) {
dataFiles.remove(pos, dataFile);
//TODO: Log error
@@ -141,4 +143,17 @@ public class DataFileHandler implements RenderDataProvider {
});
}
@Override
public void close() {
IO_MANAGER.shutdown();
try {
IO_MANAGER.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {}
dataFiles.values().forEach((f) -> f.close(level));
}
public void save() {
//TODO: Make it free memory that is not needed
dataFiles.values().forEach(f -> f.saveIfNeeded(level, false));
}
}
@@ -4,7 +4,6 @@ import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.a7.LodQuadTree;
import com.seibel.lod.core.objects.a7.LodSection;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
import com.seibel.lod.core.objects.a7.render.RenderBuffer;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
@@ -142,4 +141,5 @@ public class RenderBufferHandler {
public void close() {
renderBufferNodes.clear(RenderBufferNode::close);
}
}
@@ -0,0 +1,563 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render;
import com.seibel.lod.core.Config;
import com.seibel.lod.core.api.internal.InternalApiShared;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.FogColorMode;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.objects.BoolType;
import com.seibel.lod.core.objects.DHBlockPos;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.a7.DHLevel;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.objects.GLState;
import com.seibel.lod.core.render.objects.QuadElementBuffer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.gridList.EdgeDistanceBooleanGrid;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import com.seibel.lod.core.util.gridList.PosArrayGridList;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.LogManager;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
* This is where all the magic happens. <br>
* This is where LODs are draw to the world.
*
* @author James Seibel
* @version 12-12-2021
*/
public class a7LodRenderer
{
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(a7LodRenderer.class),
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
public static ConfigBasedSpamLogger tickLogger = new ConfigBasedSpamLogger(LogManager.getLogger(a7LodRenderer.class),
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get(),1);
public static final boolean ENABLE_DRAW_LAG_SPIKE_LOGGING = false;
public static final boolean ENABLE_DUMP_GL_STATE = true;
public static final long DRAW_LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS);
public static final boolean ENABLE_IBO = true;
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
public void end(String source) {
if (!ENABLE_DRAW_LAG_SPIKE_LOGGING) return;
timer = System.nanoTime() - timer;
if (timer> DRAW_LAG_SPIKE_THRESHOLD_NS) { //4 ms
EVENT_LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
}
}
}
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
public DebugMode previousDebugMode = null;
public final DHLevel level;
// The shader program
LodRenderProgram shaderProgram = null;
public QuadElementBuffer quadIBO = null;
public a7LodRenderer(DHLevel level)
{
this.level = level;
}
public void drawLODs(Mat4f baseModelViewMatrix, Mat4f baseProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
{
//=================================//
// determine if LODs should render //
//=================================//
if (MC_RENDER.playerHasBlindnessEffect())
{
// if the player is blind, don't render LODs,
// and don't change minecraft's fog
// which blindness relies on.
return;
}
if (MC_RENDER.getLightmapWrapper() == null)
return;
// get MC's shader program
// Save all MC render state
LagSpikeCatcher drawSaveGLState = new LagSpikeCatcher();
GLState currentState = new GLState();
if (ENABLE_DUMP_GL_STATE) {
tickLogger.debug("Saving GL state: {}", currentState);
}
drawSaveGLState.end("drawSaveGLState");
GLProxy glProxy = GLProxy.getInstance();
if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
if (!MC_RENDER.tryDisableVanillaFog())
canVanillaFogBeDisabled = false;
// TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
// starting here...
LagSpikeCatcher updateStatus = new LagSpikeCatcher();
updateRegenStatus(lodDim, partialTicks);
updateStatus.end("LodDrawSetup:UpdateStatus");
// FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
// The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
if (markToCleanup) {
LagSpikeCatcher drawObjectClenup = new LagSpikeCatcher();
markToCleanup = false;
cleanup(); // This will unset the isSetupComplete, causing a setup() call.
drawObjectClenup.end("drawObjectClenup");
}
//=================//
// create the LODs //
//=================//
// only regenerate the LODs if:
// 1. we want to regenerate LODs
// 2. we aren't already regenerating the LODs
// 3. we aren't waiting for the build and draw buffers to swap
// (this is to prevent thread conflicts)
LagSpikeCatcher swapBuffer = new LagSpikeCatcher();
if (partialRegen || fullRegen) {
if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(),
MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen)) {
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvailable() is true
fullRegen = false;
partialRegen = false;
}
}
swapBuffer.end("SwapBuffer");
// Get the front buffers to draw
MovableGridRingList<RenderRegion> regions = lodBufferBuilderFactory.getRenderRegions();
if (regions == null) {
// There is no vbos, which means nothing needs to be drawn. So skip rendering
return;
}
//===================//
// draw params setup //
//===================//
profiler.push("LOD draw setup");
LagSpikeCatcher drawSetup = new LagSpikeCatcher();
/*---------Set GL State--------*/
// Make sure to unbind current VBO so we don't mess up vanilla settings
LagSpikeCatcher drawGLSetup = new LagSpikeCatcher();
LagSpikeCatcher drawBindBuff = new LagSpikeCatcher();
//GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
GL32.glViewport(0,0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight());
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
drawBindBuff.end("drawBindBuff");
// set the required open GL settings
LagSpikeCatcher drawSetPolygon = new LagSpikeCatcher();
if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME
|| CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_GENMODE_WIREFRAME
|| CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_WIREFRAME
|| CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_OVERLAPPING_QUADS_WIREFRAME) {
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
//GL32.glDisable(GL32.GL_CULL_FACE);
}
else {
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GL32.glEnable(GL32.GL_CULL_FACE);
}
drawSetPolygon.end("drawSetPolygon");
LagSpikeCatcher drawEnableDepth = new LagSpikeCatcher();
GL32.glEnable(GL32.GL_DEPTH_TEST);
// GL32.glDisable(GL32.GL_DEPTH_TEST);
GL32.glDepthFunc(GL32.GL_LESS);
drawEnableDepth.end("drawEnableDepth");
drawGLSetup.end("drawGLSetup");
// enable transparent rendering
// GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
// GL32.glEnable(GL32.GL_BLEND);
GL32.glDisable(GL32.GL_BLEND);
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
/*---------Bind required objects--------*/
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
// also binds LightmapTexture, VAO, and ShaderProgram
if (!isSetupComplete) {
LagSpikeCatcher drawObjectSetup = new LagSpikeCatcher();
setup();
drawObjectSetup.end("drawObjectSetup");
} else {
LagSpikeCatcher drawShaderBind = new LagSpikeCatcher();
LodFogConfig newConfig = shaderProgram.isShaderUsable();
if (newConfig != null) {
shaderProgram.free();
shaderProgram = new LodRenderProgram(newConfig);
}
shaderProgram.bind();
drawShaderBind.end("drawShaderBind");
}
LagSpikeCatcher drawSetActiveTexture = new LagSpikeCatcher();
GL32.glActiveTexture(GL32.GL_TEXTURE0);
drawSetActiveTexture.end("drawSetActiveTexture");
LagSpikeCatcher drawCalculateParams = new LagSpikeCatcher();
//LightmapTexture lightmapTexture = new LightmapTexture();
/*---------Get required data--------*/
// Get the matrixs for rendering
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
int farPlaneBlockDistance;
// required for setupFog and setupProjectionMatrix
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
else
farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH;
drawCalculateParams.end("drawCalculateParams");
Mat4f combinedMatrix = createCombinedMatrix(baseProjectionMatrix, baseModelViewMatrix,
vanillaBlockRenderedDistance, farPlaneBlockDistance, partialTicks);
/*---------Fill uniform data--------*/
LagSpikeCatcher drawFillData = new LagSpikeCatcher();
// Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0
shaderProgram.fillUniformData(combinedMatrix,
MC_RENDER.isFogStateSpecial() ? getSpecialFogColor(partialTicks) : getFogColor(partialTicks),
0, MC.getWrappedClientWorld().getHeight(), MC.getWrappedClientWorld().getMinHeight(), farPlaneBlockDistance,
vanillaBlockRenderedDistance, MC_RENDER.isFogStateSpecial());
// Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one.
LagSpikeCatcher drawFillLightmap = new LagSpikeCatcher();
ILightMapWrapper lightmap = MC_RENDER.getLightmapWrapper();
lightmap.bind();
if (ENABLE_IBO) quadIBO.bind();
//lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels());
drawFillLightmap.end("drawFillLightmap");
drawFillData.end("DrawFillData");
//GL32.glEnable( GL32.GL_POLYGON_OFFSET_FILL );
//GL32.glPolygonOffset( 1f, 1f );
//===========//
// rendering //
//===========//
drawSetup.end("LodDrawSetup");
profiler.popPush("LOD draw");
LagSpikeCatcher draw = new LagSpikeCatcher();
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
DHBlockPos cameraBlockPos = MC_RENDER.getCameraBlockPosition();
Vec3f cameraDir = MC_RENDER.getLookAtVector();
int drawCount = 0;
{
int ox,oy,dx,dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.getSize();
int maxI = len*len;
int halfLen = len/2;
for(int i =0; i < maxI; i++){
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
Pos2D pos = regions.getCenter();
int regionX = ox+pos.x;
int regionZ = oy+pos.y;
{
RenderRegion region = regions.get(regionX, regionZ);
if (region == null) continue;
if (region.render(lodDim, cameraPos, cameraBlockPos, cameraDir,
!cullingDisabled, shaderProgram)) drawCount++;
}
}
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
int temp = dx;
dx = -dy;
dy = temp;
}
ox += dx;
oy += dy;
}
}
//if (drawCall==0)
// tickLogger.info("DrawCall Count: {}", drawCount);
//================//
// render cleanup //
//================//
draw.end("LodDraw");
profiler.popPush("LOD cleanup");
LagSpikeCatcher drawCleanup = new LagSpikeCatcher();
lightmap.unbind();
if (ENABLE_IBO) quadIBO.unbind();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
shaderProgram.unbind();
//lightmapTexture.free();
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
currentState.restore();
drawCleanup.end("LodDrawCleanup");
// end of internal LOD profiling
profiler.pop();
tickLogger.incLogTries();
}
//=================//
// Setup Functions //
//=================//
/** Setup all render objects - REQUIRES to be in render thread */
private void setup() {
if (isSetupComplete) {
EVENT_LOGGER.warn("Renderer setup called but it has already completed setup!");
return;
}
if (!GLProxy.hasInstance()) {
EVENT_LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
return;
}
EVENT_LOGGER.info("Setting up renderer");
isSetupComplete = true;
shaderProgram = new LodRenderProgram(LodFogConfig.generateFogConfig());
if (ENABLE_IBO) {
quadIBO = new QuadElementBuffer();
quadIBO.reserve(LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
}
EVENT_LOGGER.info("Renderer setup complete");
}
/** Create all buffers that will be used. */
public void setupBuffers()
{
lodBufferBuilderFactory.triggerReset();
}
private Color getFogColor(float partialTicks)
{
Color fogColor;
if (CONFIG.client().graphics().fogQuality().getFogColorMode() == FogColorMode.USE_SKY_COLOR)
fogColor = MC_RENDER.getSkyColor();
else
fogColor = MC_RENDER.getFogColor(partialTicks);
return fogColor;
}
private Color getSpecialFogColor(float partialTicks)
{
return MC_RENDER.getSpecialFogColor(partialTicks);
}
private static float calculateNearClipPlane(float distance, float partialTicks) {
double fov = MC_RENDER.getFov(partialTicks);
double aspectRatio = (double)MC_RENDER.getScreenWidth()/MC_RENDER.getScreenHeight();
return (float) (distance
/ Math.sqrt(1d + LodUtil.pow2(Math.tan(fov/180d*Math.PI/2d))
* (LodUtil.pow2(aspectRatio) + 1d)));
}
/**
* create and return a new projection matrix based on MC's projection matrix
* @param projMat this is Minecraft's current projection matrix
* @param modelMat this is Minecraft's current model matrix
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
*/
private static Mat4f createCombinedMatrix(Mat4f projMat, Mat4f modelMat, float vanillaBlockRenderedDistance,
int farPlaneBlockDistance, float partialTicks)
{
//Create a copy of the current matrix, so the current matrix isn't modified.
Mat4f lodProj = projMat.copy();
float nearClipPlane;
if (CONFIG.client().advanced().getLodOnlyMode()) {
nearClipPlane = 0.1f;
} else if (CONFIG.client().graphics().advancedGraphics().getUseExtendedNearClipPlane()) {
nearClipPlane = Math.min((vanillaBlockRenderedDistance-16f),8f*16f);
} else {
nearClipPlane = 16f;
}
//Set new far and near clip plane values.
lodProj.setClipPlanes(
calculateNearClipPlane(nearClipPlane, partialTicks),
(float)((farPlaneBlockDistance+LodUtil.REGION_WIDTH) * Math.sqrt(2)));
lodProj.multiply(modelMat);
return lodProj;
}
//======================//
// Cleanup Functions //
//======================//
/** cleanup and free all render objects. REQUIRES to be in render thread
* (Many objects are Native, outside of JVM, and need manual cleanup) */
private void cleanup() {
if (!isSetupComplete) {
EVENT_LOGGER.warn("Renderer cleanup called but Renderer has not completed setup!");
return;
}
if (!GLProxy.hasInstance()) {
EVENT_LOGGER.warn("Renderer Cleanup called but the GLProxy has never been inited!");
return;
}
isSetupComplete = false;
EVENT_LOGGER.info("Renderer Cleanup Started");
shaderProgram.free();
if (quadIBO != null) quadIBO.destroy(false);
EVENT_LOGGER.info("Renderer Cleanup Complete");
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
{
lodBufferBuilderFactory.destroyBuffers();
}
//======================//
// Other Misc Functions //
//======================//
/**
* If this is called then the next time "drawLODs" is called
* the LODs will be regenerated; the same as if the player moved.
*/
public void regenerateLODsNextFrame()
{
fullRegen = true;
}
// returns whether anything changed
private boolean updateVanillaRenderedChunks(LodDimension lodDim) {
// if the player is high enough, draw all LODs
IWorldWrapper world = MC.getWrappedClientWorld();
if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight() ||
CONFIG.client().advanced().getLodOnlyMode()) {
if (vanillaChunks != null) {
vanillaChunks = null;
return true;
}
return false;
}
LagSpikeCatcher getChunks = new LagSpikeCatcher();
EdgeDistanceBooleanGrid edgeGrid = LodUtil.readVanillaRenderedChunks(lodDim);
if (edgeGrid == null) {
if (vanillaChunks != null) {
vanillaChunks = null;
return true;
}
return false;
}
getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks");
PosArrayGridList<BoolType> grid = new PosArrayGridList<>(edgeGrid.gridSize, edgeGrid.getOffsetX(), edgeGrid.getOffsetY());
int overdrawOffset = LodUtil.computeOverdrawOffset(lodDim);
edgeGrid.flagAllWithDistance(grid, (i) -> (i >= overdrawOffset));
vanillaChunks = grid;
return true;
}
private void updateRegenStatus(LodDimension lodDim, float partialTicks) {
short chunkRenderDistance = (short) MC_RENDER.getRenderDistance();
long newTime = System.currentTimeMillis();
DHBlockPos newPos = MC.getPlayerBlockPos();
boolean shouldUpdateChunks = false;
boolean tryPartialGen = false;
boolean tryFullGen = false;
// check if the view distance or config changed
if (InternalApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance()
|| chunkRenderDistance != prevRenderDistance
|| prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance())
{
DetailDistanceUtil.updateSettings(); // FIXME: This should NOT be here!
prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
prevRenderDistance = chunkRenderDistance;
tryFullGen = true;
} else if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode)
{ // did the user change the debug setting?
previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode();
tryFullGen = true;
}
// check if the player has moved
if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) {
if (lastUpdatedPos == null
|| Math.abs(newPos.getX() - lastUpdatedPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16
|| Math.abs(newPos.getZ() - lastUpdatedPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16)
{
shouldUpdateChunks = true;
}
prevPlayerPosTime = newTime;
}
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
{
shouldUpdateChunks = true;
prevVanillaChunkTime = newTime;
}
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
{
tryPartialGen = true;
prevChunkTime = newTime;
}
shouldUpdateChunks |= tryFullGen;
if (shouldUpdateChunks) {
lastUpdatedPos = newPos;
tryPartialGen |= updateVanillaRenderedChunks(lodDim);
}
if (tryFullGen) {
fullRegen = true;
} else if (tryPartialGen) {
partialRegen = true;
}
}
}
@@ -0,0 +1,32 @@
package com.seibel.lod.core.util;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
public class EventLoop {
private final ExecutorService executorService;
private final Runnable runnable;
private CompletableFuture<Void> future;
public EventLoop(ExecutorService executorService, Runnable runnable) {
this.executorService = executorService;
this.runnable = runnable;
}
public void tick() {
if (future != null && future.isDone()) {
try {
future.join();
} catch (Exception ignored) {} finally {future = null;}
}
if (future == null) {
future = CompletableFuture.runAsync(runnable, executorService);
}
}
public void halt() {
if (future != null) {
future.cancel(true);
}
}
public boolean isRunning() {
return future != null && !future.isDone();
}
}
@@ -0,0 +1,7 @@
package com.seibel.lod.core.wrapperInterfaces.minecraft;
public interface IMinecraftSharedWrapper {
boolean isServerJar();
}
@@ -27,6 +27,10 @@ import com.seibel.lod.core.handlers.dependencyInjection.IBindable;
*/
public interface IProfilerWrapper extends IBindable
{
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profiler to measures
void push(String newSection);
void popPush(String newSection);