diff --git a/build.gradle b/build.gradle index 2a452ea..e07d2ea 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ if(project.enable_lombok.toBoolean()) { apply plugin: 'io.freefair.lombok' lombok { // the version of the lombok plugin we use would use 1.18.16 by default - version = '1.18.30' + version = '1.18.32' } } diff --git a/buildscript/forge-1.7.gradle b/buildscript/forge-1.7.gradle index 24f8e07..755877d 100644 --- a/buildscript/forge-1.7.gradle +++ b/buildscript/forge-1.7.gradle @@ -6,18 +6,18 @@ ext.publishDir = project.multiproject_structure.toBoolean() ? "${projectDir}/../ def getCommitVersion(){ try { - def commitHashProc = "python3 ${ext.publishDir}/get_version.py".execute() + def commitHashProc = "git describe --always --dirty".execute() commitHashProc.waitFor() if(commitHashProc.exitValue() == 0){ def commitHash = commitHashProc.text.trim() - + return commitHash } else { println commitHashProc.err.text - throw new Exception("get_version.py exited with non-zero return value") + throw new Exception("git describe exited with non-zero return value") } } catch(Exception e){ - println "Failed to run get_version.py: " + e.getMessage() + println "Failed to get commit version: " + e.getMessage() } return "UNKNOWN" // fallback } diff --git a/project.gradle b/project.gradle index c6d2f72..42a43f8 100644 --- a/project.gradle +++ b/project.gradle @@ -13,6 +13,9 @@ repositories { maven { url = "https://mvn.falsepattern.com/releases" } + maven { + url = "https://mvn.falsepattern.com/stripped" + } } dependencies { @@ -20,6 +23,13 @@ dependencies { compileOnly("com.falsepattern:rple-mc1.7.10:1.0.0-rc8:api") compileOnly("com.falsepattern:falsetweaks-mc1.7.10:2.7.4:api") + + compileOnly("optifine:shadersmod-stripped:1.7.10-hd_u7") + + // LWJGL 2 + implementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209") + implementation("org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209") + implementation("org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209") } runClient { diff --git a/publish/build.gradle b/publish/build.gradle index 1a05d54..524c610 100644 --- a/publish/build.gradle +++ b/publish/build.gradle @@ -11,18 +11,18 @@ import java.util.concurrent.TimeUnit def getCommitVersion(){ try { - def commitHashProc = "python3 ${projectDir}/get_version.py".execute() + def commitHashProc = "git describe --always --dirty".execute() commitHashProc.waitFor() if(commitHashProc.exitValue() == 0){ def commitHash = commitHashProc.text.trim() - + return commitHash } else { println commitHashProc.err.text - throw new Exception("get_version.py exited with non-zero return value") + throw new Exception("git describe exited with non-zero return value") } } catch(Exception e){ - println "Failed to run get_version.py: " + e.getMessage() + println "Failed to get commit version: " + e.getMessage() } return "UNKNOWN" // fallback } diff --git a/src/main/java/makamys/neodymium/Compat.java b/src/main/java/makamys/neodymium/Compat.java index 82c7ffb..0fee908 100644 --- a/src/main/java/makamys/neodymium/Compat.java +++ b/src/main/java/makamys/neodymium/Compat.java @@ -1,5 +1,6 @@ package makamys.neodymium; +import com.falsepattern.falsetweaks.api.ThreadedChunkUpdates; import com.falsepattern.triangulator.api.ToggleableTessellator; import cpw.mods.fml.common.Loader; import makamys.neodymium.config.Config; @@ -8,9 +9,13 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.settings.GameSettings; +import net.minecraft.launchwrapper.Launch; + import org.lwjgl.opengl.GLContext; +import shadersmod.client.Shaders; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -22,17 +27,18 @@ public class Compat { private static boolean wasAdvancedOpenGLEnabled; - private static int notEnoughVRAMAmountMB = -1; - private static boolean IS_RPLE_PRESENT; private static boolean IS_FALSE_TWEAKS_PRESENT; - + private static boolean IS_HODGEPODGE_SPEEDUP_ANIMATIONS_ENABLED; private static boolean IS_ANGELICA_SPEEDUP_ANIMATIONS_ENABLED; + private static boolean IS_SHADERS_MOD_PRESENT; + private static boolean isShadersEnabled; - + + public static void init() { isGL33Supported = GLContext.getCapabilities().OpenGL33; @@ -47,16 +53,25 @@ public static void init() { if (Loader.isModLoaded("falsetweaks")) { IS_FALSE_TWEAKS_PRESENT = true; } - + + try { + if (Launch.classLoader.getClassBytes("shadersmod.client.Shaders") != null) { + IS_SHADERS_MOD_PRESENT = true; + } + } catch (IOException e) { + IS_SHADERS_MOD_PRESENT = false; + } + + IS_HODGEPODGE_SPEEDUP_ANIMATIONS_ENABLED = checkIfHodgepodgeSpeedupAnimationsIsEnabled(); IS_ANGELICA_SPEEDUP_ANIMATIONS_ENABLED = checkIfAngelicaSpeedupAnimationsIsEnabled(); LOGGER.debug("speedupAnimations compat fix will " + (isSpeedupAnimationsEnabled() ? "" : "not ") + "be enabled"); } - + public static boolean enableVanillaChunkMeshes() { return Config.enableVanillaChunkMeshes && !isFalseTweaksModPresent(); } - + public static boolean keepRenderListLogic() { return enableVanillaChunkMeshes() || Constants.KEEP_RENDER_LIST_LOGIC; } @@ -94,7 +109,7 @@ private static boolean checkIfHodgepodgeSpeedupAnimationsIsEnabled() { } return result; } - + private static boolean checkIfAngelicaSpeedupAnimationsIsEnabled() { Boolean result = null; if (Loader.isModLoaded("angelica")) { @@ -125,7 +140,15 @@ public static boolean isRPLEModPresent() { public static boolean isFalseTweaksModPresent() { return IS_FALSE_TWEAKS_PRESENT; } - + + public static Tessellator tessellator() { + if (IS_FALSE_TWEAKS_PRESENT) { + return FalseTweaksCompat.getThreadTessellator(); + } else { + return Tessellator.instance; + } + } + public static boolean isSpeedupAnimationsEnabled() { return IS_HODGEPODGE_SPEEDUP_ANIMATIONS_ENABLED || IS_ANGELICA_SPEEDUP_ANIMATIONS_ENABLED; } @@ -135,22 +158,20 @@ public static boolean isOptiFineShadersEnabled() { } public static void updateOptiFineShadersState() { - try { - Class shaders = Class.forName("shadersmod.client.Shaders"); - try { - String shaderPack = (String)shaders.getMethod("getShaderPackName").invoke(null); - if(shaderPack != null) { - isShadersEnabled = true; - return; - } - } catch(Exception e) { - LOGGER.warn("Failed to get shader pack name"); - e.printStackTrace(); - } - } catch (ClassNotFoundException e) { + isShadersEnabled = false; + if (!IS_SHADERS_MOD_PRESENT) + return; + if (Shaders.getShaderPackName() != null) { + isShadersEnabled = true; } - isShadersEnabled = false; + } + + public static boolean isShadersShadowPass() { + if (!IS_SHADERS_MOD_PRESENT) + return false; + + return Shaders.isShadowPass; } private static void disableTriangulator() { @@ -165,9 +186,6 @@ public static void getCompatibilityWarnings(List warns, List c if(!isGL33Supported) { criticalWarns.add(new Warning("OpenGL 3.3 is not supported.")); } - if(detectedNotEnoughVRAM()) { - criticalWarns.add(new Warning("Not enough VRAM")); - } } public static boolean hasChanged() { @@ -182,18 +200,6 @@ public static boolean hasChanged() { return changed; } - public static void onNotEnoughVRAM(int amountMB) { - notEnoughVRAMAmountMB = amountMB; - } - - public static void reset() { - notEnoughVRAMAmountMB = -1; - } - - private static boolean detectedNotEnoughVRAM() { - return Config.VRAMSize == notEnoughVRAMAmountMB; - } - public static void forceEnableOptiFineDetectionOfFastCraft() { if(Compat.class.getResource("/fastcraft/Tweaker.class") != null) { // If OptiFine is present, it's already on the class path at this point, so our virtual jar won't override it. @@ -232,7 +238,19 @@ public InputStream getInputStream(String path) { } } - + + //This extra bit of indirection is needed to avoid accidentally trying to load ThreadedChunkUpdates when FalseTweaks + // is not installed. + private static class FalseTweaksCompat { + public static Tessellator getThreadTessellator() { + if (ThreadedChunkUpdates.isEnabled()) { + return ThreadedChunkUpdates.getThreadTessellator(); + } else { + return Tessellator.instance; + } + } + } + public static class Warning { public String text; public String chatAction; diff --git a/src/main/java/makamys/neodymium/Neodymium.java b/src/main/java/makamys/neodymium/Neodymium.java index 4f78362..ffe3a8e 100644 --- a/src/main/java/makamys/neodymium/Neodymium.java +++ b/src/main/java/makamys/neodymium/Neodymium.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import lombok.val; import makamys.neodymium.renderer.compat.RenderUtil; import makamys.neodymium.renderer.compat.RenderUtilRPLE; import makamys.neodymium.renderer.compat.RenderUtilShaderRPLE; @@ -69,12 +70,16 @@ public static class ModContainer { private static World rendererWorld; public void construct(FMLConstructionEvent event) { - MCLib.init(); + if (Config.updateChecker) { + MCLib.init(); + } Compat.init(); } public void preInit(FMLPreInitializationEvent event) { - MCLibModules.updateCheckAPI.submitModTask(MODID, "@UPDATE_URL@"); + if (Config.updateChecker) { + MCLibModules.updateCheckAPI.submitModTask(MODID, "@UPDATE_URL@"); + } if(VERSION.equals("@VERSION@")) { // Allow using config GUI in dev env @@ -108,17 +113,7 @@ private void onPlayerWorldChanged(World newWorld) { List criticalWarns = warnsAndCriticalWarns.getRight(); if(criticalWarns.isEmpty()) { - boolean rple = Compat.isRPLEModPresent(); - boolean optiFineShaders = Compat.isOptiFineShadersEnabled(); - if (rple && optiFineShaders) { - util = RenderUtilShaderRPLE.INSTANCE; - } else if (optiFineShaders) { - util = RenderUtilShaders.INSTANCE; - } else if (rple) { - util = RenderUtilRPLE.INSTANCE; - } else { - util = RenderUtilVanilla.INSTANCE; - } + updateRenderUtil(); renderer = new NeoRenderer(newWorld); renderer.hasIncompatibilities = !warns.isEmpty() || !criticalWarns.isEmpty(); } @@ -141,7 +136,6 @@ public void onWorldUnload(WorldEvent.Unload event) { public void onConnectToServer(ClientConnectedToServerEvent event) { Config.reloadConfig(); ChatUtil.resetShownChatMessages(); - Compat.reset(); WarningHelper.reset(); } @@ -261,4 +255,22 @@ public static Pair, List> showCompatStatus(boolean status return Pair.of(warns, criticalWarns); } + private static void updateRenderUtil() { + val hasRPLE = Compat.isRPLEModPresent(); + val hasShaders = Compat.isOptiFineShadersEnabled(); + + if (hasShaders) { + if (hasRPLE) { + util = RenderUtilShaderRPLE.INSTANCE; + } else { + util = RenderUtilShaders.INSTANCE; + } + } else { + if (hasRPLE) { + util = RenderUtilRPLE.INSTANCE; + } else { + util = RenderUtilVanilla.INSTANCE; + } + } + } } diff --git a/src/main/java/makamys/neodymium/config/Config.java b/src/main/java/makamys/neodymium/config/Config.java index 8cad6a0..27110cd 100644 --- a/src/main/java/makamys/neodymium/config/Config.java +++ b/src/main/java/makamys/neodymium/config/Config.java @@ -45,6 +45,8 @@ public class Config { public static boolean enabled; @ConfigBoolean(cat="_general", def=false, com="Apply changes made in the config file immediately without having to manually reload the renderer. Off by default because it could potentially cause poor performance on certain platforms.") public static boolean hotswap; + @ConfigBoolean(cat="_general", def=true, com="Set this to false to disable update checking.") + public static boolean updateChecker; @ConfigBoolean(cat="render", def=true, com="Don't submit faces for rendering if they are facing away from the camera. Reduces GPU workload at the cost of increasing driver overhead. This will improve the framerate most of the time, but may reduce it if you are not fillrate-limited (such as when playing on a small resolution).") public static boolean cullFaces; @@ -58,14 +60,15 @@ public class Config { @ConfigBoolean(cat="render", def=false, com="Do fog occlusion even if fog is disabled.") public static boolean fogOcclusionWithoutFog; - @NeedsReload - @ConfigInt(cat="render", def=512, min=1, max=Integer.MAX_VALUE, com="VRAM buffer size (MB). 512 seems to be a good value on Normal render distance. Increase this if you encounter warnings about the VRAM getting full. Does not affect RAM usage.") - public static int VRAMSize; @ConfigEnum(cat="render", def="auto", clazz=AutomatableBoolean.class, com="Render fog? Slightly reduces framerate. `auto` means the OpenGL setting will be respected (as set by mods like OptiFine).\nValid values: true, false, auto") public static AutomatableBoolean renderFog; @ConfigInt(cat="render", def=Integer.MAX_VALUE, min=0, max=Integer.MAX_VALUE, com="Chunks further away than this distance (in chunks) will not have unaligned quads such as tall grass rendered.") public static int maxUnalignedQuadDistance; - + @ConfigInt(cat="render", def=256,min=16,max=1024,com = "The size of the allocation chunks for opaque geometry (in Megabytes). Requires game restart to apply.") + public static int bufferSizePass0; + @ConfigInt(cat="render", def=64,min=16,max=1024,com = "The size of the allocation chunks for transparent geometry (in Megabytes). Requires game restart to apply.") + public static int bufferSizePass1; + @ConfigBoolean(cat="misc", def=true, com="Replace splash that says 'OpenGL 1.2!' with 'OpenGL 3.3!'. Just for fun.") public static boolean replaceOpenGLSplash; @ConfigBoolean(cat="misc", def=false, com="Don't warn about incompatibilities in chat, and activate renderer even in spite of critical ones.") diff --git a/src/main/java/makamys/neodymium/ducks/IMixinGameSettings_OptiFine.java b/src/main/java/makamys/neodymium/ducks/IMixinGameSettings_OptiFine.java index f7f4850..ba74ed6 100644 --- a/src/main/java/makamys/neodymium/ducks/IMixinGameSettings_OptiFine.java +++ b/src/main/java/makamys/neodymium/ducks/IMixinGameSettings_OptiFine.java @@ -1,7 +1,5 @@ package makamys.neodymium.ducks; public interface IMixinGameSettings_OptiFine { - - public int getOfFogType(); - + int nd$getOfFogType(); } diff --git a/src/main/java/makamys/neodymium/ducks/ITessellator.java b/src/main/java/makamys/neodymium/ducks/ITessellator.java deleted file mode 100644 index 1e2598f..0000000 --- a/src/main/java/makamys/neodymium/ducks/ITessellator.java +++ /dev/null @@ -1,7 +0,0 @@ -package makamys.neodymium.ducks; - -public interface ITessellator { - - public void enableMeshCapturing(boolean enable); - -} diff --git a/src/main/java/makamys/neodymium/ducks/IWorldRenderer.java b/src/main/java/makamys/neodymium/ducks/IWorldRenderer.java deleted file mode 100644 index 3c3d7a8..0000000 --- a/src/main/java/makamys/neodymium/ducks/IWorldRenderer.java +++ /dev/null @@ -1,10 +0,0 @@ -package makamys.neodymium.ducks; - -import java.util.List; - -import makamys.neodymium.renderer.ChunkMesh; - -public interface IWorldRenderer { - public List getChunkMeshes(); - public boolean isDrawn(); -} diff --git a/src/main/java/makamys/neodymium/ducks/NeodymiumTessellator.java b/src/main/java/makamys/neodymium/ducks/NeodymiumTessellator.java new file mode 100644 index 0000000..830341d --- /dev/null +++ b/src/main/java/makamys/neodymium/ducks/NeodymiumTessellator.java @@ -0,0 +1,9 @@ +package makamys.neodymium.ducks; + +import makamys.neodymium.renderer.ChunkMesh; + +public interface NeodymiumTessellator { + ChunkMesh nd$getCaptureTarget(); + void nd$setCaptureTarget(ChunkMesh target); + boolean nd$captureData(); +} diff --git a/src/main/java/makamys/neodymium/ducks/NeodymiumWorldRenderer.java b/src/main/java/makamys/neodymium/ducks/NeodymiumWorldRenderer.java new file mode 100644 index 0000000..0a2e45f --- /dev/null +++ b/src/main/java/makamys/neodymium/ducks/NeodymiumWorldRenderer.java @@ -0,0 +1,13 @@ +package makamys.neodymium.ducks; + +import java.util.List; + +import makamys.neodymium.renderer.ChunkMesh; + +public interface NeodymiumWorldRenderer { + List nd$getChunkMeshes(); + ChunkMesh nd$beginRenderPass(int pass); + void nd$endRenderPass(ChunkMesh mesh); + boolean nd$isDrawn(); + void nd$suppressRenderPasses(boolean suppressed); +} diff --git a/src/main/java/makamys/neodymium/mixin/MixinGameSettings_OptiFine.java b/src/main/java/makamys/neodymium/mixin/MixinGameSettings_OptiFine.java index 2e3fb0d..b10eefc 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinGameSettings_OptiFine.java +++ b/src/main/java/makamys/neodymium/mixin/MixinGameSettings_OptiFine.java @@ -11,14 +11,12 @@ @Mixin(value = GameSettings.class, remap = false) @Pseudo -public class MixinGameSettings_OptiFine implements IMixinGameSettings_OptiFine { - +public abstract class MixinGameSettings_OptiFine implements IMixinGameSettings_OptiFine { @Dynamic @Shadow private int ofFogType; - public int getOfFogType() { + public int nd$getOfFogType() { return ofFogType; } - } diff --git a/src/main/java/makamys/neodymium/mixin/MixinGuiMainMenu.java b/src/main/java/makamys/neodymium/mixin/MixinGuiMainMenu.java index 2851830..30a93a0 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinGuiMainMenu.java +++ b/src/main/java/makamys/neodymium/mixin/MixinGuiMainMenu.java @@ -11,7 +11,6 @@ @Mixin(GuiMainMenu.class) abstract class MixinGuiMainMenu { - @Shadow private String splashText; @@ -21,5 +20,4 @@ abstract class MixinGuiMainMenu { private void postConstructor(CallbackInfo ci) { splashText = Neodymium.modifySplash(splashText); } - } diff --git a/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal.java b/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal.java index bdbc56a..8288626 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal.java +++ b/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal.java @@ -5,7 +5,6 @@ import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -17,13 +16,13 @@ /** Blocks vanilla chunk rendering while NeoRenderer is active. */ @Mixin(RenderGlobal.class) -abstract class MixinRenderGlobal { - +abstract class MixinRenderGlobal { + @Unique + private boolean nd$isInsideUpdateRenderers; + @Shadow private WorldRenderer[] sortedWorldRenderers; - - private boolean nd$isInsideUpdateRenderers; - + @Inject(method = "renderAllRenderLists", at = @At(value = "HEAD"), cancellable = true, diff --git a/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal_OptiFine.java b/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal_OptiFine.java index 8d491d0..f56ca5d 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal_OptiFine.java +++ b/src/main/java/makamys/neodymium/mixin/MixinRenderGlobal_OptiFine.java @@ -14,7 +14,6 @@ /** Blocks vanilla chunk rendering while NeoRenderer is active. (OptiFine compat) */ @Mixin(RenderGlobal.class) abstract class MixinRenderGlobal_OptiFine { - // for OptiFine's Fast Render option @Dynamic @Redirect(method = "renderSortedRenderersFast", @@ -27,5 +26,4 @@ private void redirectRenderAllRenderLists(IntBuffer buffer) { GL11.glCallLists(buffer); } } - } diff --git a/src/main/java/makamys/neodymium/mixin/MixinTessellator.java b/src/main/java/makamys/neodymium/mixin/MixinTessellator.java index 935b860..2f962e4 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinTessellator.java +++ b/src/main/java/makamys/neodymium/mixin/MixinTessellator.java @@ -2,43 +2,60 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import makamys.neodymium.config.Config; -import makamys.neodymium.ducks.ITessellator; +import makamys.neodymium.ducks.NeodymiumTessellator; import makamys.neodymium.renderer.ChunkMesh; import net.minecraft.client.renderer.Tessellator; @Mixin(Tessellator.class) -abstract class MixinTessellator implements ITessellator { - - private boolean nd$captureMeshes; - - @Shadow protected abstract void reset(); - - @Shadow private boolean isDrawing; - - @Inject(method = "draw", - at = @At(value = "HEAD"), - cancellable = true, - require = 1) - private void preDraw(CallbackInfoReturnable cir) { - if(nd$captureMeshes) { - ChunkMesh.preTessellatorDraw((Tessellator)(Object)this); - +abstract class MixinTessellator implements NeodymiumTessellator { + // ---additions--- + @Unique + private ChunkMesh nd$captureTarget; + + @Override + public boolean nd$captureData() { + if(nd$captureTarget != null) { + nd$captureTarget.addTessellatorData((Tessellator)(Object)this); + if(Config.enabled && !Config.enableVanillaChunkMeshes) { isDrawing = false; reset(); - cir.setReturnValue(0); + return true; } } + return false; + } + + @Override + public ChunkMesh nd$getCaptureTarget() { + return nd$captureTarget; } - + @Override - public void enableMeshCapturing(boolean enable) { - nd$captureMeshes = enable; + public void nd$setCaptureTarget(ChunkMesh target) { + nd$captureTarget = target; + } + + // ---mixins--- + + @Shadow + public abstract void reset(); + @Shadow + private boolean isDrawing; + + @Inject(method = "draw", + at = @At(value = "HEAD"), + cancellable = true, + require = 1) + private void preDraw(CallbackInfoReturnable cir) { + if (nd$captureData()) { + cir.setReturnValue(0); + } } - } diff --git a/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java b/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java index 725b6f8..14b1995 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java +++ b/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java @@ -3,11 +3,11 @@ import com.google.common.collect.Lists; import makamys.neodymium.Compat; import makamys.neodymium.Neodymium; -import makamys.neodymium.ducks.ITessellator; -import makamys.neodymium.ducks.IWorldRenderer; +import makamys.neodymium.ducks.NeodymiumTessellator; +import makamys.neodymium.ducks.NeodymiumWorldRenderer; import makamys.neodymium.renderer.ChunkMesh; import makamys.neodymium.renderer.NeoRenderer; -import net.minecraft.client.renderer.Tessellator; + import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.entity.EntityLivingBase; @@ -26,46 +26,109 @@ /** Inserts hooks in WorldRenderer to listen for changes, and to grab the tessellator data right before rendering. */ @Mixin(WorldRenderer.class) -abstract class MixinWorldRenderer implements IWorldRenderer { - +abstract class MixinWorldRenderer implements NeodymiumWorldRenderer { + // ---additions--- + + @Unique + private boolean nd$savedDrawnStatus; + @Unique + private List nd$chunkMeshes; + @Unique + private boolean nd$renderPassSuppressed; + + @Override + public void nd$suppressRenderPasses(boolean suppressed) { + this.nd$renderPassSuppressed = suppressed; + } + + @Override + public List nd$getChunkMeshes() { + return nd$chunkMeshes; + } + + @Override + public ChunkMesh nd$beginRenderPass(int pass) { + if(Neodymium.isActive() && !nd$renderPassSuppressed) { + ChunkMesh cm = new ChunkMesh((WorldRenderer)(Object)this, pass); + ((NeodymiumTessellator)Compat.tessellator()).nd$setCaptureTarget(cm); + return cm; + } + return null; + } + + @Override + public void nd$endRenderPass(ChunkMesh chunkMesh) { + if(Neodymium.isActive() && !nd$renderPassSuppressed) { + if (chunkMesh != null) { + chunkMesh.finishConstruction(); + } + ((NeodymiumTessellator)Compat.tessellator()).nd$setCaptureTarget(null); + } + } + + @Override + public boolean nd$isDrawn() { + return !(skipRenderPass[0] && skipRenderPass[1]); + } + + @Unique + private void nd$reset(boolean sort) { + nd$saveDrawnStatus(); + + if(Neodymium.isActive()) { + if(nd$chunkMeshes != null) { + Collections.fill(nd$chunkMeshes, null); + } else { + nd$chunkMeshes = Lists.newArrayList(null, null); + } + } + } + + @Unique + private void nd$postUpdateRenderer(boolean sort) { + nd$notifyIfDrawnStatusChanged(); + + if(Neodymium.isActive()) { + if(nd$chunkMeshes != null) { + Neodymium.renderer.onWorldRendererPost((WorldRenderer) (Object) this, sort); + Collections.fill(nd$chunkMeshes, null); + } + } + } + + @Unique + private void nd$saveDrawnStatus() { + nd$savedDrawnStatus = nd$isDrawn(); + } + + @Unique + private void nd$notifyIfDrawnStatusChanged() { + boolean drawn = nd$isDrawn(); + if(Neodymium.isActive() && drawn != nd$savedDrawnStatus) { + Neodymium.renderer.onWorldRendererChanged((WorldRenderer) (Object) this, drawn ? NeoRenderer.WorldRendererChange.VISIBLE : NeoRenderer.WorldRendererChange.INVISIBLE); + } + } + + // ---mixins--- + @Shadow - private boolean isInFrustum; + public boolean isInFrustum; @Shadow public boolean[] skipRenderPass; - - @Shadow - public boolean needsUpdate; - - private boolean nd$savedDrawnStatus; - - private List nd$chunkMeshes; - + // Inject before first instruction inside if(needsUpdate) block @Inject(method = {"updateRenderer"}, at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/WorldRenderer;needsUpdate:Z", ordinal = 0), require = 1) private void preUpdateRenderer(CallbackInfo ci) { - preUpdateRenderer(false); + nd$reset(false); } - + @Inject(method = {"updateRendererSort"}, at = @At(value = "HEAD"), require = 1) private void preUpdateRendererSort(CallbackInfo ci) { - preUpdateRenderer(true); - } - - @Unique - private void preUpdateRenderer(boolean sort) { - saveDrawnStatus(); - - if(Neodymium.isActive()) { - if(nd$chunkMeshes != null) { - Collections.fill(nd$chunkMeshes, null); - } else { - nd$chunkMeshes = Lists.newArrayList(null, null); - } - } + nd$reset(true); } // Inject after last instruction inside if(needsUpdate) block @@ -73,38 +136,21 @@ private void preUpdateRenderer(boolean sort) { at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/WorldRenderer;isInitialized:Z", ordinal = 0, shift = Shift.AFTER), require = 1) private void postUpdateRenderer(CallbackInfo ci) { - postUpdateRenderer(false); + nd$postUpdateRenderer(false); } - + @Inject(method = {"updateRendererSort"}, at = @At(value = "RETURN"), require = 1) private void postUpdateRendererSort(CallbackInfo ci) { - postUpdateRenderer(true); + nd$postUpdateRenderer(true); } - - @Unique - private void postUpdateRenderer(boolean sort) { - notifyIfDrawnStatusChanged(); - - if(Neodymium.isActive()) { - if(nd$chunkMeshes != null) { - Neodymium.renderer.onWorldRendererPost(WorldRenderer.class.cast(this), sort); - Collections.fill(nd$chunkMeshes, null); - } - } - } - + @Inject(method = "preRenderBlocks", at = @At("HEAD"), require = 1) private void prePreRenderBlocks(int pass, CallbackInfo ci) { - if(Neodymium.isActive()) { - ((ITessellator)Tessellator.instance).enableMeshCapturing(true); - ChunkMesh cm = new ChunkMesh((WorldRenderer)(Object)this, pass); - nd$chunkMeshes.set(pass, cm); - ChunkMesh.setCaptureTarget(cm); - } + nd$chunkMeshes.set(pass, nd$beginRenderPass(pass)); } @Redirect(method = "preRenderBlocks", @@ -125,76 +171,34 @@ private void noEndList() { if (!Neodymium.isActive() || Compat.keepRenderListLogic()) GL11.glEndList(); } - - @Inject(method = "postRenderBlocks", - at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"), - require = 1) - private void prePostRenderBlocks(int pass, EntityLivingBase entity, CallbackInfo ci) { - /*if(Neodymium.isActive()) { - if(nd$chunkMeshes != null) { - if(nd$chunkMeshes.get(pass) == null) { - nd$chunkMeshes.set(pass, ChunkMesh.fromTessellator(pass, WorldRenderer.class.cast(this))); - } - nd$chunkMeshes.get(pass).addTessellatorData(Tessellator.instance); - } - }*/ - } - + @Inject(method = "postRenderBlocks", at = @At("RETURN"), require = 1) private void postPostRenderBlocks(int pass, EntityLivingBase entity, CallbackInfo ci) { - if(Neodymium.isActive()) { - nd$chunkMeshes.get(pass).finishConstruction(); - ((ITessellator)Tessellator.instance).enableMeshCapturing(false); - ChunkMesh.setCaptureTarget(null); - } + nd$endRenderPass(nd$chunkMeshes.get(pass)); } - + @Inject(method = "setDontDraw", at = @At(value = "HEAD"), require = 1) private void preSetDontDraw(CallbackInfo ci) { if(Neodymium.isActive()) { - Neodymium.renderer.onWorldRendererChanged(WorldRenderer.class.cast(this), NeoRenderer.WorldRendererChange.DELETED); + Neodymium.renderer.onWorldRendererChanged((WorldRenderer) (Object) this, NeoRenderer.WorldRendererChange.DELETED); } } - - @Override - public List getChunkMeshes() { - return nd$chunkMeshes; - } - + @Inject(method = "updateInFrustum", at = @At(value = "HEAD"), require = 1) private void preUpdateInFrustum(CallbackInfo ci) { - saveDrawnStatus(); + nd$saveDrawnStatus(); } - + @Inject(method = "updateInFrustum", at = @At(value = "RETURN"), require = 1) private void postUpdateInFrustum(CallbackInfo ci) { - notifyIfDrawnStatusChanged(); - } - - @Unique - private void saveDrawnStatus() { - nd$savedDrawnStatus = isDrawn(); - } - - @Unique - private void notifyIfDrawnStatusChanged() { - boolean drawn = isDrawn(); - if(Neodymium.isActive() && drawn != nd$savedDrawnStatus) { - Neodymium.renderer.onWorldRendererChanged(WorldRenderer.class.cast(this), drawn ? NeoRenderer.WorldRendererChange.VISIBLE : NeoRenderer.WorldRendererChange.INVISIBLE); - } - } - - @Override - public boolean isDrawn() { - return (isInFrustum || Compat.isOptiFineShadersEnabled()) && !(skipRenderPass[0] && skipRenderPass[1]); + nd$notifyIfDrawnStatusChanged(); } } diff --git a/src/main/java/makamys/neodymium/renderer/ChunkMesh.java b/src/main/java/makamys/neodymium/renderer/ChunkMesh.java index 9f1e8fc..0132c1a 100644 --- a/src/main/java/makamys/neodymium/renderer/ChunkMesh.java +++ b/src/main/java/makamys/neodymium/renderer/ChunkMesh.java @@ -6,19 +6,19 @@ import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import gnu.trove.list.array.TIntArrayList; import lombok.val; -import makamys.neodymium.Compat; import makamys.neodymium.Neodymium; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import makamys.neodymium.config.Config; -import makamys.neodymium.ducks.IWorldRenderer; +import makamys.neodymium.ducks.NeodymiumWorldRenderer; import makamys.neodymium.util.BufferWriter; -import makamys.neodymium.util.RecyclingList; import makamys.neodymium.util.Util; import makamys.neodymium.util.WarningHelper; import net.minecraft.client.Minecraft; @@ -35,27 +35,27 @@ public class ChunkMesh extends Mesh { private int[] subMeshStart = new int[NORMAL_ORDER.length]; - public static int usedRAM = 0; - public static int instances = 0; - - private static RecyclingList quadBuf = new RecyclingList<>(() -> new MeshQuad()); - - private static ChunkMesh meshCaptureTarget; - + public static final AtomicLong usedRAM = new AtomicLong(); + public static final AtomicInteger instances = new AtomicInteger(); + + public static final ThreadLocal quadBuf = ThreadLocal.withInitial(QuadMeshBuffer::new); + private static final QuadNormal[] NORMAL_ORDER = new QuadNormal[] {QuadNormal.NONE, QuadNormal.POSITIVE_Y, QuadNormal.POSITIVE_X, QuadNormal.POSITIVE_Z, QuadNormal.NEGATIVE_X, QuadNormal.NEGATIVE_Z, QuadNormal.NEGATIVE_Y}; - private static final Comparator MESH_QUAD_RENDER_COMPARATOR = new MeshQuadRenderOrderComparator(); private static final int[] QUAD_NORMAL_TO_NORMAL_ORDER; - + private static final int[] NORMAL_ORDER_TO_QUAD_NORMAL; + private static final Flags FLAGS = new Flags(true, true, true, false); static { QUAD_NORMAL_TO_NORMAL_ORDER = new int[QuadNormal.values().length]; + NORMAL_ORDER_TO_QUAD_NORMAL = new int[QuadNormal.values().length]; for(int i = 0; i < QuadNormal.values().length; i++) { int idx = Arrays.asList(NORMAL_ORDER).indexOf(QuadNormal.values()[i]); if(idx == -1) { idx = 0; } QUAD_NORMAL_TO_NORMAL_ORDER[i] = idx; + NORMAL_ORDER_TO_QUAD_NORMAL[idx] = i; } } @@ -67,20 +67,14 @@ public ChunkMesh(WorldRenderer wr, int pass) { this.pass = pass; Arrays.fill(subMeshStart, -1); - instances++; + instances.getAndIncrement(); - if(!quadBuf.getAsList().isEmpty()) { + if(!quadBuf.get().isEmpty()) { LOGGER.error("Invalid state: tried to construct a chunk mesh before the previous one has finished constructing!"); } } - - public static void preTessellatorDraw(Tessellator t) { - if(meshCaptureTarget != null) { - meshCaptureTarget.addTessellatorData(t); - } - } - - private void addTessellatorData(Tessellator t) { + + public void addTessellatorData(Tessellator t) { tesselatorDataCount++; if(t.vertexCount == 0) { @@ -101,29 +95,30 @@ private void addTessellatorData(Tessellator t) { if(!t.hasColor) { warnings.add("Color data is missing"); } - if(t.hasNormals && GL11.glIsEnabled(GL11.GL_LIGHTING)) { - errors.add("Chunk uses GL lighting, this is not implemented."); - } + // TODO This opengl call crashes the JVM when not run on the client thread. +// if(t.hasNormals && GL11.glIsEnabled(GL11.GL_LIGHTING)) { +// errors.add("Chunk uses GL lighting, this is not implemented."); +// } FLAGS.hasBrightness = t.hasBrightness; FLAGS.hasColor = t.hasColor; int verticesPerPrimitive = t.drawMode == GL11.GL_QUADS ? 4 : 3; - int tessellatorVertexSize = 8; - if (Compat.isOptiFineShadersEnabled()) - tessellatorVertexSize += 10; - if (Compat.isRPLEModPresent()) - tessellatorVertexSize += 4; - - for(int quadI = 0; quadI < t.vertexCount / verticesPerPrimitive; quadI++) { - MeshQuad quad = quadBuf.next(); - quad.setState(t.rawBuffer, tessellatorVertexSize, quadI * (verticesPerPrimitive * tessellatorVertexSize), FLAGS, t.drawMode, NeoRegion.toRelativeOffset(-t.xOffset), NeoRegion.toRelativeOffset(-t.yOffset), NeoRegion.toRelativeOffset(-t.zOffset)); - if(quad.deleted) { - quadBuf.remove(); + int tessellatorVertexSize = Neodymium.util.vertexSizeInTessellator(); + int quadSize = Neodymium.util.quadSize(); + + int quadCount = t.vertexCount / verticesPerPrimitive; + + val buf = quadBuf.get(); + buf.ensureCapacity(quadCount * quadSize); + for(int quadI = 0; quadI < quadCount; quadI++) { + boolean deleted = MeshQuad.processQuad(t.rawBuffer, quadI * verticesPerPrimitive * tessellatorVertexSize, buf.data, buf.size, NeoRegion.toRelativeOffset(-t.xOffset), NeoRegion.toRelativeOffset(-t.yOffset), NeoRegion.toRelativeOffset(-t.zOffset), t.drawMode, FLAGS); + if (!deleted) { + buf.size += quadSize; } } - if(!quadBuf.isEmpty()) { + if(!buf.isEmpty()) { // Only show errors if we're actually supposed to be drawing something if(!errors.isEmpty() || !warnings.isEmpty()) { if(!Config.silenceErrors) { @@ -145,7 +140,7 @@ private void addTessellatorData(Tessellator t) { e.printStackTrace(); } LOGGER.error("Skipping chunk due to errors."); - quadBuf.reset(); + buf.reset(); } else { WarningHelper.showDebugMessageOnce(String.format("Warnings in chunk (%d, %d, %d) in dimension %s: %s", x, y, z, dimId, String.join(", ", warnings))); } @@ -157,52 +152,55 @@ private void addTessellatorData(Tessellator t) { private static String tessellatorToString(Tessellator t) { return "(" + t.xOffset + ", " + t.yOffset + ", " + t.zOffset + ")"; } - + + private int bufferSize = 0; + @Override + public int bufferSize() { + return bufferSize; + } + public void finishConstruction() { - List quads = quadBuf.getAsList(); + val buf = quadBuf.get(); + quadCount = buf.size / Neodymium.util.quadSize(); + buffer = createBuffer(buf.data); + bufferSize = buffer.limit(); + usedRAM.getAndAdd(bufferSize); - quadCount = countValidQuads(quads); - buffer = createBuffer(quads, quadCount); - usedRAM += buffer.limit(); - - quadBuf.reset(); + buf.reset(); } - - private static int countValidQuads(List quads) { - int quadCount = 0; - for(MeshQuad quad : quads) { - if(!quad.deleted) { - quadCount++; - } + + //Used by FalseTweaks when cancelling a threaded render job + public static void cancelRendering() { + val buf = quadBuf.get(); + if (!buf.isEmpty()) { + buf.reset(); + LOGGER.debug("Cancelled unfinished render pass!"); } - return quadCount; } - private ByteBuffer createBuffer(List quads, int quadCount) { + private static final ThreadLocal threadBucketer = ThreadLocal.withInitial( + MeshQuadBucketSort::new); + + private ByteBuffer createBuffer(int[] quads) { val stride = Neodymium.renderer.getStride(); ByteBuffer buffer = BufferUtils.createByteBuffer(quadCount * 4 * stride); BufferWriter out = new BufferWriter(buffer); boolean sortByNormals = pass == 0; - + + val quadSize = Neodymium.util.quadSize(); + int[] indices = null; if(sortByNormals) { - quads.sort(MESH_QUAD_RENDER_COMPARATOR); + indices = threadBucketer.get().sort(quads, quadSize, quadCount); } - - int i = 0; - for(MeshQuad quad : quads) { - if(i < quadCount) { - if(MeshQuad.isValid(quad)) { - int subMeshStartIdx = sortByNormals ? QUAD_NORMAL_TO_NORMAL_ORDER[quad.normal.ordinal()] : 0; - if(subMeshStart[subMeshStartIdx] == -1) { - subMeshStart[subMeshStartIdx] = i; - } - Neodymium.util.writeMeshQuadToBuffer(quad, out, stride); - i++; - } else if(sortByNormals){ - break; - } + + for (int i = 0; i < quadCount; i++) { + int index = indices != null ? indices[i] : i; + int subMeshStartIdx = sortByNormals ? QUAD_NORMAL_TO_NORMAL_ORDER[quads[(index + 1) * quadSize - 1]] : 0; + if(subMeshStart[subMeshStartIdx] == -1) { + subMeshStart[subMeshStartIdx] = i; } + Neodymium.util.writeMeshQuadToBuffer(quads, index * quadSize, out, stride); } @@ -212,8 +210,8 @@ private ByteBuffer createBuffer(List quads, int quadCount) { void destroy() { if(buffer != null) { - usedRAM -= buffer.limit(); - instances--; + usedRAM.getAndAdd(-buffer.limit()); + instances.getAndDecrement(); buffer = null; if(gpuStatus == Mesh.GPUStatus.SENT) { @@ -236,7 +234,7 @@ static List getChunkMesh(int theX, int theY, int theZ) { wr.chunkIndex = 0; wr.markDirty(); wr.updateRenderer(Minecraft.getMinecraft().thePlayer); - return ((IWorldRenderer)wr).getChunkMeshes(); + return ((NeodymiumWorldRenderer)wr).nd$getChunkMeshes(); } @Override @@ -297,10 +295,6 @@ public double distSq(Entity player) { return player.getDistanceSq(centerX, centerY, centerZ); } - - public static void setCaptureTarget(ChunkMesh cm) { - meshCaptureTarget = cm; - } public static class Flags { public boolean hasTexture; @@ -339,20 +333,64 @@ public byte toByte() { return flags; } } - - private static class MeshQuadRenderOrderComparator implements Comparator { - - @Override - public int compare(MeshQuad a, MeshQuad b) { - if(!MeshQuad.isValid(b)) { - return -1; - } else if(!MeshQuad.isValid(a)) { - return 1; - } else { - return QUAD_NORMAL_TO_NORMAL_ORDER[a.normal.ordinal()] - QUAD_NORMAL_TO_NORMAL_ORDER[b.normal.ordinal()]; + + public static class QuadMeshBuffer { + public int[] data = new int[1024]; + public int size = 0; + + public void ensureCapacity(int maxNewAmount) { + int newSize = size + maxNewAmount; + if (newSize > data.length) { + data = Arrays.copyOf(data, newSize); } } - + + public boolean isEmpty() { + return size == 0; + } + + public void reset() { + size = 0; + } + } + + public static class MeshQuadBucketSort { + private static final int bucketCount = NORMAL_ORDER_TO_QUAD_NORMAL.length; + private final TIntArrayList[] buckets; + private int[] resultBuffer; + + public MeshQuadBucketSort() { + buckets = new TIntArrayList[bucketCount]; + for (int i = 0; i < bucketCount; i++) { + buckets[i] = new TIntArrayList(); + } + } + + private static int bucket(int[] buffer, int quadSize, int index) { + return buffer[quadSize * (index + 1) - 1]; + } + + public int[] sort(int[] buffer, int quadSize, int quadCount) { + for (int i = 0; i < bucketCount; i++) { + buckets[i].resetQuick(); + } + for (int i = 0; i < quadCount; i++) { + buckets[bucket(buffer, quadSize, i)].add(i); + } + if (resultBuffer == null || resultBuffer.length < quadCount) { + resultBuffer = new int[quadCount]; + } + val result = resultBuffer; + int offset = 0; + for (int i = 0; i < bucketCount; i++) { + val bucket = buckets[NORMAL_ORDER_TO_QUAD_NORMAL[i]]; + val size = bucket.size(); + bucket.toArray(result, 0, offset, size); + offset += size; + } + assert offset == quadCount; + return result; + } } } diff --git a/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java b/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java index 6f5e4fe..257317d 100644 --- a/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java +++ b/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java @@ -1,189 +1,178 @@ package makamys.neodymium.renderer; -import static org.lwjgl.opengl.GL15.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import makamys.neodymium.Compat; +import lombok.val; +import lombok.var; import makamys.neodymium.Neodymium; -import makamys.neodymium.config.Config; import makamys.neodymium.renderer.Mesh.GPUStatus; import makamys.neodymium.util.GuiHelper; -import makamys.neodymium.util.ChatUtil; +import org.lwjgl.BufferUtils; -import static makamys.neodymium.Constants.LOGGER; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static makamys.neodymium.config.Config.bufferSizePass0; +import static makamys.neodymium.config.Config.bufferSizePass1; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL15.*; +import static org.lwjgl.opengl.GL30.*; +import static org.lwjgl.opengl.GL31.*; -/** Manages dynamic memory allocation inside a fixed buffer on the GPU. */ +/** Manages dynamic memory allocation inside a buffer on the GPU. */ public class GPUMemoryManager { - - private long bufferSize; - - public int VBO; - - private int nextMesh; - - private List sentMeshes = new ArrayList<>(); - - private long usedVRAM; - private long lastUsedVRAMUpdate; + private static final long MEGABYTE = 1024 * 1024L; + private static final long[] BUFFER_SIZE_BYTES = { + bufferSizePass0 * MEGABYTE, + bufferSizePass1 * MEGABYTE + }; + + private static final int INDEX_ALLOCATION_SIZE_BYTES = 512 * 4; + private static final long USED_VRAM_UPDATE_RATE = 1_000_000_000L; - - private static final long MAX_VRAM_FULLNESS_INTERVAL = 30L * 1_000_000_000L; - private static long lastVRAMFullness = -1; - - public GPUMemoryManager() { - VBO = glGenBuffers(); - - bufferSize = ((long)Config.VRAMSize) * 1024 * 1024; - - glBindBuffer(GL_ARRAY_BUFFER, VBO); - - glBufferData(GL_ARRAY_BUFFER, bufferSize, GL_DYNAMIC_DRAW); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - public void runGC(boolean full) { - glBindBuffer(GL_ARRAY_BUFFER, VBO); - - int moved = 0; - int timesReachedEnd = 0; - int checksLeft = sentMeshes.size(); - - final int MB256 = 256 * 1024 * 1024; - - int panicRate = (int)(((float)Math.max(MB256 - (bufferSize - end()), 0) / (float)MB256) * 64f); - - while((!full && (moved < (4 + panicRate) && checksLeft-- > 0)) || (full && timesReachedEnd < 2) && !sentMeshes.isEmpty()) { - nextMesh++; - if(nextMesh >= sentMeshes.size()) { - nextMesh = 0; - timesReachedEnd++; - } - Mesh mesh = sentMeshes.get(nextMesh); - - if(mesh.gpuStatus == GPUStatus.SENT) { - long offset = nextMesh == 0 ? 0 : sentMeshes.get(nextMesh - 1).getEnd(); - if(mesh.offset != offset) { - glBufferSubData(GL_ARRAY_BUFFER, offset, mesh.buffer); - moved++; - } - mesh.iFirst = (int)(offset / Neodymium.renderer.getStride()); - mesh.offset = offset; - } else if(mesh.gpuStatus == GPUStatus.PENDING_DELETE) { - mesh.iFirst = -1; - mesh.offset = -1; - mesh.visible = false; - mesh.gpuStatus = GPUStatus.UNSENT; - - sentMeshes.remove(nextMesh); - - mesh.destroyBuffer(); - - if(nextMesh > 0) { - nextMesh--; - } - } + + private static int copyBuffer = GL_ZERO; + private static long copyBufferSize = 0; + + private final List sentMeshes = new ArrayList<>(); + + private final long bufferSizeBytes; + + public final int managerIndex; + public final int pass; + + private int indexSize; + + private int nextMesh = 0; + + private long usedVRAM = 0; + private long lastUsedVRAMUpdate = 0; + + public int VAO = GL_ZERO; + public int VBO = GL_ZERO; + + public IntBuffer piFirst = null; + public IntBuffer piCount = null; + + public GPUMemoryManager(int managerIndex, int pass) throws Exception { + this.bufferSizeBytes = BUFFER_SIZE_BYTES[pass]; + + this.managerIndex = managerIndex; + this.pass = pass; + + try { + this.VBO = createVBO(bufferSizeBytes); + this.VAO = createVAO(); + } catch (Exception e) { + destroyImpl(); + throw e; } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - private long end() { - return (sentMeshes.isEmpty() ? 0 : sentMeshes.get(sentMeshes.size() - 1).getEnd()); + + this.indexSize = INDEX_ALLOCATION_SIZE_BYTES; + + reAllocIndexBuffers(); + piFirst.flip(); + piCount.flip(); } - - public void sendMeshToGPU(Mesh mesh) { - if(mesh == null || mesh.buffer == null) { - return; - } - - if(end() + mesh.bufferSize() >= bufferSize) { - runGC(true); - } - - if(end() + mesh.bufferSize() >= bufferSize) { - Neodymium.renderer.destroyPending = true; - - long t = System.nanoTime(); - - if(lastVRAMFullness != -1 && t - lastVRAMFullness < MAX_VRAM_FULLNESS_INTERVAL) { - ChatUtil.showNeoChatMessage("VRAM keeps getting full! Reverting to vanilla renderer. Try increasing the VRAM buffer size in the config, if possible.", ChatUtil.MessageVerbosity.ERROR, false); - Compat.onNotEnoughVRAM(Config.VRAMSize); - } else { - LOGGER.debug("Reloading renderer because VRAM is full."); - // TODO restart renderer with more VRAM allocated when this happens. - } - lastVRAMFullness = t; - return; - } - - int size = mesh.bufferSize(); - int insertIndex = -1; - - long nextBase = -1; + + public boolean uploadMesh(Mesh mesh) { + if(mesh == null || mesh.buffer == null) + return false; + + if(end() + mesh.bufferSize() >= bufferSizeBytes) + return false; + + val size = mesh.bufferSize(); + var insertIndex = -1; + + var nextBase = -1L; if(!sentMeshes.isEmpty()) { if(nextMesh < sentMeshes.size() - 1) { - Mesh next = sentMeshes.get(nextMesh); - Mesh nextnext = null; - for(int i = nextMesh + 1; i < sentMeshes.size(); i++) { - Mesh m = sentMeshes.get(i); - if(m.gpuStatus == Mesh.GPUStatus.SENT) { - nextnext = m; + val meshA = sentMeshes.get(nextMesh); + Mesh meshB = null; + for(var i = nextMesh + 1; i < sentMeshes.size(); i++) { + val meshC = sentMeshes.get(i); + if(meshC.gpuStatus == Mesh.GPUStatus.SENT) { + meshB = meshC; break; } } - if(nextnext != null && nextnext.offset - next.getEnd() >= size) { - nextBase = next.getEnd(); + if(meshB != null && meshB.offset - meshA.getEnd() >= size) { + nextBase = meshA.getEnd(); insertIndex = nextMesh + 1; } } - - if(nextBase == -1) { + + if(nextBase == -1) nextBase = sentMeshes.get(sentMeshes.size() - 1).getEnd(); - } } - if(nextBase == -1) nextBase = 0; - - - if(mesh.gpuStatus == GPUStatus.UNSENT) { - mesh.prepareBuffer(); - - glBindBuffer(GL_ARRAY_BUFFER, VBO); - - glBufferSubData(GL_ARRAY_BUFFER, nextBase, mesh.buffer); - mesh.iFirst = (int)(nextBase / Neodymium.renderer.getStride()); - mesh.iCount = mesh.quadCount * 4; - mesh.offset = nextBase; - - if(insertIndex == -1) { + if (nextBase == -1) + nextBase = 0; + + if (mesh.gpuStatus == GPUStatus.UNSENT) { + uploadMeshToVBO(mesh, nextBase); + + if (insertIndex == -1) { sentMeshes.add(mesh); } else { sentMeshes.add(insertIndex, mesh); nextMesh = insertIndex; } - - glBindBuffer(GL_ARRAY_BUFFER, 0); } - + mesh.gpuStatus = GPUStatus.SENT; + mesh.attachedManager = this; + return true; } - - public void deleteMeshFromGPU(Mesh mesh) { - if(mesh == null || mesh.gpuStatus == GPUStatus.UNSENT) { + + public void deleteMesh(Mesh mesh) { + if(mesh == null || mesh.gpuStatus == GPUStatus.UNSENT) return; - } mesh.gpuStatus = GPUStatus.PENDING_DELETE; + mesh.attachedManager = null; } - public void destroy() { - glDeleteBuffers(VBO); + public void growIndexBuffers() { + indexSize *= 1.5F; + + reAllocIndexBuffers(); + + piFirst.limit(piFirst.capacity()); + piCount.limit(piCount.capacity()); } - public List getDebugText() { - long t = System.nanoTime(); + public void runGC(boolean full) { + var moved = 0; + var timesReachedEnd = 0; + var checksLeft = sentMeshes.size(); + + while ((!full && moved < 32 && checksLeft > 0) || full && timesReachedEnd < 2 && !sentMeshes.isEmpty()) { + checksLeft--; + nextMesh++; + if (nextMesh >= sentMeshes.size()) { + nextMesh = 0; + timesReachedEnd++; + } + val mesh = sentMeshes.get(nextMesh); + + if (mesh.gpuStatus == GPUStatus.SENT) { + val offset = nextMesh == 0 ? 0 : sentMeshes.get(nextMesh - 1).getEnd(); + if (mesh.offset != offset) { + moveMeshInVBO(mesh, offset); + moved++; + } + } else if (mesh.gpuStatus == GPUStatus.PENDING_DELETE) { + deleteMeshFromVBO(mesh); + sentMeshes.remove(nextMesh); + if (nextMesh > 0) + nextMesh--; + } + } + } + + public List debugText() { + final long t = System.nanoTime(); if(t - lastUsedVRAMUpdate > USED_VRAM_UPDATE_RATE) { usedVRAM = 0; for(Mesh mesh : sentMeshes) { @@ -191,20 +180,19 @@ public List getDebugText() { } lastUsedVRAMUpdate = t; } - return Arrays.asList("VRAM: " + (usedVRAM / 1024 / 1024) + "MB (" + (end() / 1024 / 1024) + "MB) / " + (bufferSize / 1024 / 1024) + "MB"); + return Collections.singletonList("PASS " + pass + ": " + (usedVRAM / 1024 / 1024) + "MB (" + (end() / 1024 / 1024) + "MB) / " + (bufferSizeBytes / 1024 / 1024) + "MB"); } - public void drawInfo() { + public int drawDebugInfo(int yOff) { int scale = 10000; int rowLength = 512; - int yOff = 20; - - int height = (int)(bufferSize / scale) / rowLength; + + int height = (int)(bufferSizeBytes / scale) / rowLength; GuiHelper.drawRectangle(0, yOff, rowLength, height, 0x000000, 50); - + int meshI = 0; for(Mesh mesh : sentMeshes) { - + int o = (int)(mesh.offset / 10000); int o2 = (int)((mesh.offset + mesh.bufferSize()) / 10000); if(o / rowLength == o2 / rowLength) { @@ -223,11 +211,166 @@ public void drawInfo() { meshI++; } GuiHelper.drawRectangle(0 % rowLength, 0 + yOff, 4, 4, 0x00FF00); - GuiHelper.drawRectangle((int)(bufferSize / scale) % rowLength, (int)(bufferSize / scale) / rowLength + yOff, 4, 4, 0xFF0000); + GuiHelper.drawRectangle((int)(bufferSizeBytes / scale) % rowLength, (int)(bufferSizeBytes / scale) / rowLength + yOff, 4, 4, 0xFF0000); + return (int)(bufferSizeBytes / scale) / rowLength + yOff; } - - public float getCoherenceRate() { - return (float)ChunkMesh.usedRAM / (float)end(); + + public void destroy() { + destroyCopyBuffer(); + + if (Neodymium.renderer != null) { + NeoRenderer.submitTask(this::destroyImpl, 60); + } else { + destroyImpl(); + } + } + + private void uploadMeshToVBO(Mesh mesh, long offset) { + mesh.prepareBuffer(); + if (mesh.bufferSize() > 0) + copyBytesToVBO(offset, mesh.buffer); + + mesh.iFirst = (int) (offset / Neodymium.renderer.getStride()); + mesh.iCount = mesh.quadCount * 4; + mesh.offset = offset; + } + + private void deleteMeshFromVBO(Mesh mesh) { + deleteBytesFromVBO(mesh.offset, mesh.bufferSize()); + mesh.iFirst = -1; + mesh.offset = -1; + mesh.visible = false; + mesh.gpuStatus = GPUStatus.UNSENT; + mesh.destroyBuffer(); + } + + private void reAllocIndexBuffers() { + piFirst = refreshIntBuffer(piFirst, BufferUtils.createByteBuffer(indexSize * 4).asIntBuffer()); + piCount = refreshIntBuffer(piCount, BufferUtils.createByteBuffer(indexSize * 4).asIntBuffer()); + } + + private void moveMeshInVBO(Mesh mesh, long newOffset) { + moveBytesInVBO(mesh.offset, newOffset, mesh.bufferSize()); + mesh.iFirst = (int) (newOffset / Neodymium.renderer.getStride()); + mesh.offset = newOffset; + } + + private void copyBytesToVBO(long offset, ByteBuffer bytes) { + if (bytes.remaining() == 0) + return; + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferSubData(GL_ARRAY_BUFFER, offset, bytes); + glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO); } + private void moveBytesInVBO(long sourceOffsetBytes, long targetOffsetBytes, long sizeBytes) { + if (sizeBytes == 0) + return; + + val targetEndBytes = targetOffsetBytes + sizeBytes; + val sourceEndBytes = sourceOffsetBytes + sizeBytes; + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + if (sourceOffsetBytes <= targetEndBytes && sourceEndBytes >= targetOffsetBytes) { + prepareCopyBuffer(sizeBytes); + + glBindBuffer(GL_COPY_WRITE_BUFFER, copyBuffer); + + glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, sourceOffsetBytes, 0, sizeBytes); + glCopyBufferSubData(GL_COPY_WRITE_BUFFER, GL_ARRAY_BUFFER, 0, targetOffsetBytes, sizeBytes); + glBindBuffer(GL_COPY_WRITE_BUFFER, GL_ZERO); + } else { + glCopyBufferSubData(GL_ARRAY_BUFFER, GL_ARRAY_BUFFER, sourceOffsetBytes, targetOffsetBytes, sizeBytes); + } + glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO); + } + + private void deleteBytesFromVBO(long offsetBytes, long sizeBytes) { + // NO-OP + } + + private long end() { + return sentMeshes.isEmpty() ? 0 : sentMeshes.get(sentMeshes.size() - 1).getEnd(); + } + + private void destroyImpl() { + if (VAO != GL_ZERO) { + glDeleteVertexArrays(VAO); + VAO = GL_ZERO; + } + if (VBO != GL_ZERO) { + glDeleteBuffers(VBO); + VBO = GL_ZERO; + } + } + + private static int createVBO(long sizeBytes) throws Exception { + flushGLError(); + + val vbo = glGenBuffers(); + if (vbo == GL_ZERO) + throw new Exception("Failed to create new VBO"); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeBytes, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO); + + if (checkGLError()) { + glDeleteBuffers(vbo); + throw new Exception("Failed to allocate " + sizeBytes + " bytes for new VBO"); + } + + return vbo; + } + + private static int createVAO() throws Exception { + val vao = glGenVertexArrays(); + if (vao == GL_ZERO) + throw new Exception("Failed to create vertex array"); + return vao; + } + + private static void flushGLError() { + while (glGetError() != GL_NO_ERROR) ; + } + + private static boolean checkGLError() { + return glGetError() != GL_NO_ERROR; + } + + private static IntBuffer refreshIntBuffer(IntBuffer oldBuf, IntBuffer newBuf) { + if (oldBuf != null) { + newBuf.position(oldBuf.position()); + } + return newBuf; + } + + private static void prepareCopyBuffer(long requiredSizeBytes) { + if (copyBufferSize >= requiredSizeBytes) + return; + + if (copyBuffer == GL_ZERO) + copyBuffer = glGenBuffers(); + copyBufferSize = next16Megabyte(requiredSizeBytes); + + glBindBuffer(GL_COPY_WRITE_BUFFER, copyBuffer); + glBufferData(GL_COPY_WRITE_BUFFER, copyBufferSize, GL_DYNAMIC_COPY); + glBindBuffer(GL_COPY_WRITE_BUFFER, GL_ZERO); + } + + private static void destroyCopyBuffer() { + if (copyBuffer == GL_ZERO) + return; + + glDeleteBuffers(copyBuffer); + copyBuffer = GL_ZERO; + copyBufferSize = 0; + } + + private static long next16Megabyte(long size) { + val sixteenMegs = 16 * MEGABYTE; + val increments = size / sixteenMegs + 1; + return increments * sixteenMegs; + } } diff --git a/src/main/java/makamys/neodymium/renderer/Mesh.java b/src/main/java/makamys/neodymium/renderer/Mesh.java index 1fe5003..cef31bd 100644 --- a/src/main/java/makamys/neodymium/renderer/Mesh.java +++ b/src/main/java/makamys/neodymium/renderer/Mesh.java @@ -13,6 +13,7 @@ public abstract class Mesh { public int quadCount; public boolean visible; public GPUStatus gpuStatus = GPUStatus.UNSENT; + public GPUMemoryManager attachedManager = null; public int iFirst = -1, iCount = -1; public long offset = -1; public int pass; diff --git a/src/main/java/makamys/neodymium/renderer/MeshQuad.java b/src/main/java/makamys/neodymium/renderer/MeshQuad.java index cfae0b3..eb7edfe 100644 --- a/src/main/java/makamys/neodymium/renderer/MeshQuad.java +++ b/src/main/java/makamys/neodymium/renderer/MeshQuad.java @@ -1,98 +1,64 @@ package makamys.neodymium.renderer; -import makamys.neodymium.Compat; +import lombok.experimental.UtilityClass; +import lombok.val; import makamys.neodymium.Neodymium; -import makamys.neodymium.config.Config; -import makamys.neodymium.util.BufferWriter; import makamys.neodymium.util.Util; -import org.lwjgl.opengl.GL11; import org.lwjgl.util.vector.Vector3f; -import java.util.Locale; +import static makamys.neodymium.renderer.compat.RenderUtil.QUAD_OFFSET_XPOS; +import static makamys.neodymium.renderer.compat.RenderUtil.QUAD_OFFSET_YPOS; +import static makamys.neodymium.renderer.compat.RenderUtil.QUAD_OFFSET_ZPOS; -public class MeshQuad { +@UtilityClass +public final class MeshQuad { public final static int DEFAULT_BRIGHTNESS = Util.createBrightness(15, 15); public final static int DEFAULT_COLOR = 0xFFFFFFFF; - //region common - public float[] xs = new float[4]; - public float[] ys = new float[4]; - public float[] zs = new float[4]; - public float[] us = new float[4]; - public float[] vs = new float[4]; - public int[] cs = new int[4]; - // TODO normals? - public int[] bs = new int[4]; - - //endregion common - - //region RPLE - - // bs used as RED - public int[] bsG = new int[4]; - public int[] bsB = new int[4]; - - //endregion RPLE - - //region Shaders - - public int[] e1 = new int[4]; - public int[] e2 = new int[4]; - - public float[] xn = new float[4]; - public float[] yn = new float[4]; - public float[] zn = new float[4]; - - public float[] xt = new float[4]; - public float[] yt = new float[4]; - public float[] zt = new float[4]; - public float[] wt = new float[4]; - - public float[] um = new float[4]; - public float[] vm = new float[4]; - - //endregion Shaders - - //region Shaders + RPLE - - public float[] ue = new float[4]; - public float[] ve = new float[4]; - - //endregion Shaders + RPLE - - public boolean deleted; + private static class Vectors { + public final Vector3f A = new Vector3f(); + public final Vector3f B = new Vector3f(); + public final Vector3f C = new Vector3f(); + } - public QuadNormal normal; + private static final ThreadLocal VECTORS = ThreadLocal.withInitial(Vectors::new); + + public static boolean processQuad(int[] tessBuffer, int tessOffset, int[] quadBuffer, int quadOffset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { + val util = Neodymium.util; + util.readMeshQuad(tessBuffer, tessOffset, quadBuffer, quadOffset, offsetX, offsetY, offsetZ, drawMode, flags); + int stride = util.vertexSizeInQuadBuffer(); + boolean deleted = true; + for (int i = 1; i < 4; i++) { + int offset = quadOffset + stride * i; + if (quadBuffer[quadOffset + QUAD_OFFSET_XPOS] != quadBuffer[offset + QUAD_OFFSET_XPOS] || + quadBuffer[quadOffset + QUAD_OFFSET_YPOS] != quadBuffer[offset + QUAD_OFFSET_YPOS] || + quadBuffer[quadOffset + QUAD_OFFSET_ZPOS] != quadBuffer[offset + QUAD_OFFSET_ZPOS]) { + deleted = false; + break; + } + } - private static Vector3f vectorA = new Vector3f(); - private static Vector3f vectorB = new Vector3f(); - private static Vector3f vectorC = new Vector3f(); + if (deleted) + return true; - public void setState(int[] rawBuffer, int tessellatorVertexSize, int offset, ChunkMesh.Flags flags, int drawMode, float offsetX, float offsetY, float offsetZ) { - deleted = false; + float X0 = Float.intBitsToFloat(quadBuffer[quadOffset + QUAD_OFFSET_XPOS]); + float Y0 = Float.intBitsToFloat(quadBuffer[quadOffset + QUAD_OFFSET_YPOS]); + float Z0 = Float.intBitsToFloat(quadBuffer[quadOffset + QUAD_OFFSET_ZPOS]); + float X1 = Float.intBitsToFloat(quadBuffer[quadOffset + stride + QUAD_OFFSET_XPOS]); + float Y1 = Float.intBitsToFloat(quadBuffer[quadOffset + stride + QUAD_OFFSET_YPOS]); + float Z1 = Float.intBitsToFloat(quadBuffer[quadOffset + stride + QUAD_OFFSET_ZPOS]); + float X2 = Float.intBitsToFloat(quadBuffer[quadOffset + stride * 2 + QUAD_OFFSET_XPOS]); + float Y2 = Float.intBitsToFloat(quadBuffer[quadOffset + stride * 2 + QUAD_OFFSET_YPOS]); + float Z2 = Float.intBitsToFloat(quadBuffer[quadOffset + stride * 2 + QUAD_OFFSET_ZPOS]); - Neodymium.util.readMeshQuad(this, rawBuffer, tessellatorVertexSize, offset, offsetX, offsetY, offsetZ, drawMode, flags); + val vectors = VECTORS.get(); + vectors.A.set(X1 - X0, Y1 - Y0, Z1 - Z0); + vectors.B.set(X2 - X1, Y2 - Y1, Z2 - Z1); + Vector3f.cross(vectors.A, vectors.B, vectors.C); - if(xs[0] == xs[1] && xs[1] == xs[2] && xs[2] == xs[3] && ys[0] == ys[1] && ys[1] == ys[2] && ys[2] == ys[3]) { - // ignore empty quads (e.g. alpha pass of EnderIO item conduits) - deleted = true; - return; - } - - vectorA.set(xs[1] - xs[0], ys[1] - ys[0], zs[1] - zs[0]); - vectorB.set(xs[2] - xs[1], ys[2] - ys[1], zs[2] - zs[1]); - Vector3f.cross(vectorA, vectorB, vectorC); - - normal = QuadNormal.fromVector(vectorC); - } + quadBuffer[quadOffset + stride * 4] = QuadNormal.fromVector(vectors.C).ordinal(); - @Override - public String toString() { - return String.format(Locale.ENGLISH, "%s[(%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f)]", deleted ? "XXX " : "", xs[0], ys[0], zs[0], xs[1], ys[1], zs[1], xs[2], ys[2], zs[2], xs[3], ys[3], zs[3]); - } - - public static boolean isValid(MeshQuad q) { - return q != null && !q.deleted; + return false; } } diff --git a/src/main/java/makamys/neodymium/renderer/NeoRegion.java b/src/main/java/makamys/neodymium/renderer/NeoRegion.java index ea3347a..84b7f9f 100644 --- a/src/main/java/makamys/neodymium/renderer/NeoRegion.java +++ b/src/main/java/makamys/neodymium/renderer/NeoRegion.java @@ -13,19 +13,26 @@ public class NeoRegion { private NeoChunk[][] data = new NeoChunk[SIZE][SIZE]; @Getter - private RenderData renderData; + private List renderData = new ArrayList<>(); int regionX, regionZ; public int meshes = 0; private int emptyTicks = 0; + + public RenderData getRenderData(GPUMemoryManager manager) { + int index = manager.managerIndex; + while (renderData.size() <= index) { + renderData.add(new RenderData(regionX * SIZE * 16, 0, regionZ * SIZE * 16)); + } + return renderData.get(index); + } public NeoRegion(int regionX, int regionZ) { this.regionX = regionX; this.regionZ = regionZ; - this.renderData = new RenderData(regionX * SIZE * 16, 0, regionZ * SIZE * 16); - + for(int i = 0; i < SIZE; i++) { for(int j = 0; j < SIZE; j++) { data[i][j] = new NeoChunk(regionX * SIZE + i, regionZ * SIZE + j, this); @@ -106,21 +113,18 @@ public boolean shouldDelete() { public static class RenderData { public final double originX, originY, originZ; - private List[] sentMeshes = (List[])new ArrayList[] {new ArrayList(), new ArrayList()}; - public int[] batchLimit = new int[2]; - public int[] batchFirst = new int[2]; + @Getter + private final List sentMeshes = new ArrayList(); + public int batchLimit; + public int batchFirst; public void sort(double eyePosX, double eyePosY, double eyePosZ, boolean pass0, boolean pass1) { if(pass0) { - sentMeshes[0].sort(Comparators.MESH_DISTANCE_COMPARATOR.setOrigin(eyePosX, eyePosY, eyePosZ).setInverted(false)); + sentMeshes.sort(Comparators.MESH_DISTANCE_COMPARATOR.setOrigin(eyePosX, eyePosY, eyePosZ).setInverted(false)); } if(pass1) { - sentMeshes[1].sort(Comparators.MESH_DISTANCE_COMPARATOR.setOrigin(eyePosX, eyePosY, eyePosZ).setInverted(true)); + sentMeshes.sort(Comparators.MESH_DISTANCE_COMPARATOR.setOrigin(eyePosX, eyePosY, eyePosZ).setInverted(true)); } } - - public List getSentMeshes(int i) { - return sentMeshes[i]; - } - } + } } diff --git a/src/main/java/makamys/neodymium/renderer/NeoRenderer.java b/src/main/java/makamys/neodymium/renderer/NeoRenderer.java index d7d7e1d..b63fc1a 100644 --- a/src/main/java/makamys/neodymium/renderer/NeoRenderer.java +++ b/src/main/java/makamys/neodymium/renderer/NeoRenderer.java @@ -1,30 +1,24 @@ package makamys.neodymium.renderer; -import com.falsepattern.falsetweaks.api.triangulator.VertexAPI; -import com.falsepattern.rple.api.client.RPLELightMapUtil; -import com.falsepattern.rple.api.client.RPLEShaderConstants; import lombok.val; +import lombok.var; import makamys.neodymium.Compat; import makamys.neodymium.Neodymium; import makamys.neodymium.config.Config; -import makamys.neodymium.ducks.IWorldRenderer; +import makamys.neodymium.ducks.NeodymiumWorldRenderer; import makamys.neodymium.renderer.Mesh.GPUStatus; import makamys.neodymium.renderer.attribs.AttributeSet; import makamys.neodymium.util.*; +import makamys.neodymium.util.Util; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.RenderGlobal; import net.minecraft.client.renderer.WorldRenderer; -import net.minecraft.entity.Entity; import net.minecraft.util.EnumChatFormatting; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.World; import org.lwjgl.BufferUtils; import org.lwjgl.input.Keyboard; -import org.lwjgl.opengl.ARBVertexShader; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL13; -import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.*; import org.lwjgl.util.vector.Matrix4f; import org.lwjgl.util.vector.Vector4f; @@ -32,15 +26,15 @@ import java.nio.IntBuffer; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; import static makamys.neodymium.Constants.VERSION; -import static org.lwjgl.opengl.GL11.glGetInteger; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL14.glMultiDrawArrays; import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER; import static org.lwjgl.opengl.GL15.glBindBuffer; import static org.lwjgl.opengl.GL20.*; -import static org.lwjgl.opengl.GL30.*; +import static org.lwjgl.opengl.GL30.glBindVertexArray; /** * The main renderer class. @@ -48,6 +42,7 @@ public class NeoRenderer { public boolean hasInited = false; + public boolean isFirstPass = true; public boolean destroyPending; public boolean reloadPending; public int rendererSpeedup; @@ -63,14 +58,10 @@ public class NeoRenderer { private boolean fogEnabled; - private static int MAX_MESHES; - - private int VAO; private int[] shaderProgramsFog = {0, 0}; private int[] shaderProgramsNoFog = {0, 0}; - private IntBuffer[] piFirst = new IntBuffer[2]; - private IntBuffer[] piCount = new IntBuffer[2]; - GPUMemoryManager mem; + private List mems = new ArrayList<>(); + private Map> memMap = new HashMap<>(); private AttributeSet attributes; private Map loadedRegionsMap = new HashMap<>(); @@ -93,7 +84,8 @@ public class NeoRenderer { int eyePosYTDiv; int eyePosZTDiv; - private int renderedMeshes, renderedQuads; + private int renderedMeshesRender, renderedQuadsRender; + private int renderedMeshesShadow, renderedQuadsShadow; private int frameCount; public NeoRenderer(World world) { @@ -108,74 +100,99 @@ public NeoRenderer(World world) { Vector4f transformedOrigin = new Vector4f(); + + private int gcCounter = 0; public int preRenderSortedRenderers(int renderPass, double alpha, WorldRenderer[] sortedWorldRenderers) { - int rendered = 0; - if (hasInited) { - if (renderPass == 0) { - renderedMeshes = 0; - renderedQuads = 0; - - mainLoop(); - if (Minecraft.getMinecraft().currentScreen == null) { - handleKeyboard(); - } - if (mem.getCoherenceRate() < 0.95f || frameCount % 4 == 0) { - mem.runGC(false); - } + if (!hasInited) + return 0; - if (rendererActive && renderWorld) { - updateGLValues(); + val mc = Minecraft.getMinecraft(); + val opaquePass = renderPass == 0; + val isShadowPass = Compat.isShadersShadowPass(); + val shouldRender = rendererActive && renderWorld; - transformedOrigin.set(0, 0, 0, 1); - Matrix4f.transform(modelViewMatrixInv, transformedOrigin, transformedOrigin); + if (isFirstPass) { + renderedMeshesRender = 0; + renderedQuadsRender = 0; + renderedMeshesShadow = 0; + renderedQuadsShadow = 0; - Entity rve = Minecraft.getMinecraft().renderViewEntity; + mainLoop(); + if (mc.currentScreen == null) + handleKeyboard(); + } - eyePosX = rve.lastTickPosX + (rve.posX - rve.lastTickPosX) * alpha; - eyePosY = rve.lastTickPosY + (rve.posY - rve.lastTickPosY) * alpha + rve.getEyeHeight(); - eyePosZ = rve.lastTickPosZ + (rve.posZ - rve.lastTickPosZ) * alpha; + var rendered = 0; + if (shouldRender) { + if (opaquePass) { + updateGLValues(); + updateEyePos(alpha); - eyePosXT = eyePosX + transformedOrigin.x; - eyePosYT = eyePosY + transformedOrigin.y; - eyePosZT = eyePosZ + transformedOrigin.z; + if (!isShadowPass) + sortMeshes(frameCount % 100 == 0, frameCount % Config.sortFrequency == 0); - eyePosXTDiv = Math.floorDiv((int) Math.floor(eyePosXT), 16); - eyePosYTDiv = Math.floorDiv((int) Math.floor(eyePosYT), 16); - eyePosZTDiv = Math.floorDiv((int) Math.floor(eyePosZT), 16); + initIndexBuffers(isShadowPass); + } - sort(frameCount % 100 == 0, frameCount % Config.sortFrequency == 0); + if (isFirstPass && !Compat.keepRenderListLogic() && !Compat.isFalseTweaksModPresent()) + updateRenderGlobalStats(); - initIndexBuffers(); - - if(!Compat.keepRenderListLogic() && !Compat.isFalseTweaksModPresent()) { - updateRenderGlobalStats(); - } - } + rendered = render(renderPass, alpha); + } - frameCount++; - } + isFirstPass = false; + return rendered; + } - if (rendererActive && renderWorld) { - Minecraft.getMinecraft().entityRenderer.enableLightmap((double) alpha); + private void updateEyePos(double alpha) { + transformedOrigin.set(0, 0, 0, 1); + Matrix4f.transform(modelViewMatrixInv, transformedOrigin, transformedOrigin); - rendered += render(renderPass, alpha); + val rve = Minecraft.getMinecraft().renderViewEntity; - Minecraft.getMinecraft().entityRenderer.disableLightmap((double) alpha); - } - } - return rendered; + eyePosX = rve.lastTickPosX + (rve.posX - rve.lastTickPosX) * alpha; + eyePosY = rve.lastTickPosY + (rve.posY - rve.lastTickPosY) * alpha + rve.getEyeHeight(); + eyePosZ = rve.lastTickPosZ + (rve.posZ - rve.lastTickPosZ) * alpha; + + eyePosXT = eyePosX + transformedOrigin.x; + eyePosYT = eyePosY + transformedOrigin.y; + eyePosZT = eyePosZ + transformedOrigin.z; + + eyePosXTDiv = Math.floorDiv((int) Math.floor(eyePosXT), 16); + eyePosYTDiv = Math.floorDiv((int) Math.floor(eyePosYT), 16); + eyePosZTDiv = Math.floorDiv((int) Math.floor(eyePosZT), 16); } public void onRenderTickEnd() { if (Neodymium.isActive()) { if (reloadPending) { Minecraft.getMinecraft().renderGlobal.loadRenderers(); + return; } - if (showMemoryDebugger && mem != null) { - GuiHelper.begin(); - mem.drawInfo(); - GuiHelper.end(); + + if (gcCounter % 4 == 0) { + for (val mem : mems) + mem.runGC(false); + } + gcCounter++; + + if (showMemoryDebugger) { + int yOff = 20; + boolean drawing = false; + for (val mem : mems) { + if (mem != null) { + if (!drawing) { + drawing = true; + GuiHelper.begin(); + } + yOff = mem.drawDebugInfo(yOff) + 10; + } + } + if (drawing) + GuiHelper.end(); } + + isFirstPass = true; } else if (destroyPending) { destroy(); destroyPending = false; @@ -184,47 +201,67 @@ public void onRenderTickEnd() { } } - private void sort(boolean pass0, boolean pass1) { - for (NeoRegion r : loadedRegionsMap.values()) { - r.getRenderData().sort(eyePosX, eyePosY, eyePosZ, pass0, pass1); + private void sortMeshes(boolean pass0, boolean pass1) { + for (val mem: mems) { + for (NeoRegion r : loadedRegionsMap.values()) { + r.getRenderData(mem).sort(eyePosX, eyePosY, eyePosZ, pass0, pass1); + } } } - private void initIndexBuffers() { + private boolean isRendererVisible(WorldRenderer wr, boolean shadowPass) { + return shadowPass || wr.isVisible; + } + + private void initIndexBuffers(boolean shadowPass) { loadedRegionsList.clear(); loadedRegionsList.addAll(loadedRegionsMap.values()); loadedRegionsList.sort(Comparators.REGION_DISTANCE_COMPARATOR.setOrigin(eyePosX, eyePosY, eyePosZ)); - for (int i = 0; i < 2; i++) { - piFirst[i].limit(MAX_MESHES); - piCount[i].limit(MAX_MESHES); - int order = i == 0 ? 1 : -1; + for (val mem: mems) { + mem.piFirst.clear(); + mem.piCount.clear(); + int order = mem.pass == 0 ? 1 : -1; for (int regionI = order == 1 ? 0 : loadedRegionsList.size() - 1; regionI >= 0 && regionI < loadedRegionsList.size(); regionI += order) { - NeoRegion.RenderData region = loadedRegionsList.get(regionI).getRenderData(); - region.batchFirst[i] = piFirst[i].position(); - for (Mesh mesh : region.getSentMeshes(i)) { + NeoRegion.RenderData region = loadedRegionsList.get(regionI).getRenderData(mem); + region.batchFirst = mem.piFirst.position(); + for (Mesh mesh : region.getSentMeshes()) { WorldRenderer wr = ((ChunkMesh) mesh).wr; - if (mesh.visible && wr.isVisible && shouldRenderMesh(mesh)) { - int meshes = mesh.writeToIndexBuffer(piFirst[i], piCount[i], eyePosXTDiv, eyePosYTDiv, eyePosZTDiv, i); - renderedMeshes += meshes; - for (int j = piCount[i].position() - meshes; j < piCount[i].position(); j++) { - renderedQuads += piCount[i].get(j) / 4; + if ((shadowPass || wr.isInFrustum) && mesh.visible && isRendererVisible(wr, shadowPass) && shouldRenderMesh(mesh)) { + if (mem.piFirst.position() >= mem.piFirst.limit() - 16) { + mem.growIndexBuffers(); + } + int meshes = mesh.writeToIndexBuffer(mem.piFirst, mem.piCount, eyePosXTDiv, eyePosYTDiv, eyePosZTDiv, mem.pass); + if (shadowPass) { + renderedMeshesShadow += meshes; + } else { + renderedMeshesRender += meshes; } - if(Compat.isSpeedupAnimationsEnabled() && !Compat.keepRenderListLogic()) { - // Hodgepodge hooks this method to decide what animations to play, make sure it runs - wr.getGLCallListForPass(i); + for (int j = mem.piCount.position() - meshes; j < mem.piCount.position(); j++) { + val count = mem.piCount.get(j) / 4; + if (shadowPass) { + renderedQuadsShadow += count; + } else { + renderedQuadsRender += count; + } } } + if(Compat.isSpeedupAnimationsEnabled() && !Compat.keepRenderListLogic()) { + // Hodgepodge hooks this method to decide what animations to play, make sure it runs + wr.getGLCallListForPass(mem.pass); + } } - region.batchLimit[i] = piFirst[i].position(); + region.batchLimit = mem.piFirst.position(); } - piFirst[i].flip(); - piCount[i].flip(); + mem.piFirst.flip(); + mem.piCount.flip(); } } private boolean shouldRenderMesh(Mesh mesh) { - if ((Config.maxMeshesPerFrame == -1 || renderedMeshes < Config.maxMeshesPerFrame)) { + if (Compat.isShadersShadowPass()) + return true; + if ((Config.maxMeshesPerFrame == -1 || renderedMeshesRender < Config.maxMeshesPerFrame)) { if ((!isFogEnabled() && !Config.fogOcclusionWithoutFog) || Config.fogOcclusion == !Config.fogOcclusion || mesh.distSq( @@ -237,12 +274,39 @@ private boolean shouldRenderMesh(Mesh mesh) { } return false; } - + + + + private static class DelayedTask implements Comparable { + public final int timestamp; + public final Runnable task; + private final int idx; + private static final AtomicInteger IIDX = new AtomicInteger(); + public DelayedTask(int timestamp, Runnable task) { + this.timestamp = timestamp; + this.task = task; + idx = IIDX.getAndIncrement(); + } + @Override + public int compareTo(DelayedTask o) { + if (timestamp == o.timestamp) { + return Integer.compare(idx, o.idx); + } + return Integer.compare(timestamp, o.timestamp); + } + } + private static int frameCounter = 0; + private static final TreeSet tasks = new TreeSet<>(); + + public static void submitTask(Runnable task, int delayFrames) { + tasks.add(new DelayedTask(frameCounter + delayFrames, task)); + } + private static void updateRenderGlobalStats() { // Normally renderSortedRenderers does this, but we cancelled it - + RenderGlobal rg = Minecraft.getMinecraft().renderGlobal; - + for(WorldRenderer wr : rg.sortedWorldRenderers) { if(wr != null) { ++rg.renderersLoaded; @@ -260,6 +324,14 @@ private static void updateRenderGlobalStats() { } private void mainLoop() { + if (!tasks.isEmpty()) { + val task = tasks.first(); + if (task.timestamp - frameCounter < 0) { + tasks.pollFirst(); + task.task.run(); + } + } + frameCounter++; if (Minecraft.getMinecraft().playerController.netClientHandler.doneLoadingTerrain) { for (Iterator> it = loadedRegionsMap.entrySet().iterator(); it.hasNext(); ) { Entry kv = it.next(); @@ -321,6 +393,10 @@ private void handleKeyboard() { Matrix4f projMatrix = new Matrix4f(); private int render(int pass, double alpha) { + val mems = memMap.get(pass); + if (mems == null) + return 0; + if (!Compat.isOptiFineShadersEnabled()) { int shader = getShaderProgram(pass); @@ -329,8 +405,6 @@ private int render(int pass, double alpha) { updateUniforms(alpha, pass); } - glBindVertexArray(VAO); - if (isWireframeEnabled()) { GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE); } @@ -340,36 +414,42 @@ private int render(int pass, double alpha) { u_renderOffset = glGetUniformLocation(getShaderProgram(pass), "renderOffset"); } - int oldLimit = piFirst[pass].limit(); + val er = Minecraft.getMinecraft().entityRenderer; + er.enableLightmap(alpha); - int rendered = 0; - int order = pass == 0 ? 1 : -1; - for (int regionI = order == 1 ? 0 : loadedRegionsList.size() - 1; regionI >= 0 && regionI < loadedRegionsList.size(); regionI += order) { - NeoRegion.RenderData region = loadedRegionsList.get(regionI).getRenderData(); - rendered += region.batchLimit[pass] - region.batchFirst[pass]; - Util.setPositionAndLimit(piFirst[pass], region.batchFirst[pass], region.batchLimit[pass]); - Util.setPositionAndLimit(piCount[pass], region.batchFirst[pass], region.batchLimit[pass]); + var rendered = 0; + for (val mem: mems) { + glBindVertexArray(mem.VAO); + int oldLimit = mem.piFirst.limit(); - if (Compat.isOptiFineShadersEnabled()) { - GL11.glMatrixMode(GL_MODELVIEW); + int order = pass == 0 ? 1 : -1; + for (int regionI = order == 1 ? 0 : loadedRegionsList.size() - 1; regionI >= 0 && regionI < loadedRegionsList.size(); regionI += order) { + NeoRegion.RenderData region = loadedRegionsList.get(regionI).getRenderData(mem); + rendered += region.batchLimit - region.batchFirst; + Util.setPositionAndLimit(mem.piFirst, region.batchFirst, region.batchLimit); + Util.setPositionAndLimit(mem.piCount, region.batchFirst, region.batchLimit); - val offsetX = (float) (region.originX - eyePosX); - val offsetY = (float) ((region.originY - eyePosY) + 0.12); - val offsetZ = (float) (region.originZ - eyePosZ); + if (Compat.isOptiFineShadersEnabled()) { + GL11.glMatrixMode(GL_MODELVIEW); - GL11.glPushMatrix(); - GL11.glTranslatef(offsetX, offsetY, offsetZ); - } else { - glUniform3f(u_renderOffset, (float) (region.originX - eyePosX), (float) (region.originY - eyePosY), (float) (region.originZ - eyePosZ)); - } + val offsetX = (float) (region.originX - eyePosX); + val offsetY = (float) ((region.originY - eyePosY) + 0.12); + val offsetZ = (float) (region.originZ - eyePosZ); + + GL11.glPushMatrix(); + GL11.glTranslatef(offsetX, offsetY, offsetZ); + } else { + glUniform3f(u_renderOffset, (float) (region.originX - eyePosX), (float) (region.originY - eyePosY), (float) (region.originZ - eyePosZ)); + } - glMultiDrawArrays(GL_QUADS, piFirst[pass], piCount[pass]); + glMultiDrawArrays(GL_QUADS, mem.piFirst, mem.piCount); - if (Compat.isOptiFineShadersEnabled()) - GL11.glPopMatrix(); + if (Compat.isOptiFineShadersEnabled()) + GL11.glPopMatrix(); + } + Util.setPositionAndLimit(mem.piFirst, 0, oldLimit); + Util.setPositionAndLimit(mem.piCount, 0, oldLimit); } - Util.setPositionAndLimit(piFirst[pass], 0, oldLimit); - Util.setPositionAndLimit(piCount[pass], 0, oldLimit); if (isWireframeEnabled()) { GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); @@ -380,6 +460,8 @@ private int render(int pass, double alpha) { } glBindVertexArray(0); + + er.disableLightmap(alpha); return rendered; } @@ -466,10 +548,6 @@ private void updateUniforms(double alpha, int pass) { * @implSpec The attributes here need to be kept in sync with {@link RenderUtil#writeMeshQuadToBuffer(MeshQuad, BufferWriter, int)} */ public boolean init() { - // The average mesh is 60 KB. Let's be safe and assume 8 KB per mesh. - // This means 1 MB of index data per 512 MB of VRAM. - MAX_MESHES = Config.VRAMSize * 128; - Compat.updateOptiFineShadersState(); attributes = new AttributeSet(); @@ -477,111 +555,23 @@ public boolean init() { Neodymium.util.initVertexAttributes(attributes); reloadShader(); + return true; + } - VAO = glGenVertexArrays(); - glBindVertexArray(VAO); + private GPUMemoryManager initMemoryManager(int pass) throws Exception { + val mem = new GPUMemoryManager(mems.size(), pass); + mems.add(mem); + memMap.computeIfAbsent(pass, p -> new ArrayList<>()).add(mem); - mem = new GPUMemoryManager(); + glBindVertexArray(mem.VAO); glBindBuffer(GL_ARRAY_BUFFER, mem.VBO); - boolean optiFineShaders = Compat.isOptiFineShadersEnabled(); - if (optiFineShaders) { - initOptiFineShadersVertexPointers(); - } else { - attributes.enable(); - } - - for (int i = 0; i < 2; i++) { - piFirst[i] = BufferUtils.createIntBuffer(MAX_MESHES); - piFirst[i].flip(); - piCount[i] = BufferUtils.createIntBuffer(MAX_MESHES); - piCount[i].flip(); - } + Neodymium.util.applyVertexAttributes(attributes); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); - - return true; - } - - // TODO: This format is nice, we should have it in the docs too! - // position 3 floats 12 bytes offset 0 - // texture 2 floats 8 bytes offset 12 - // color 4 bytes 4 bytes offset 20 - // brightness 2 shorts 4 bytes offset 24 (brightness_R with RPLE) - // entitydata 3 shorts 6 bytes offset 28 - // -------- 2 bytes offset 34 - // normal 3 floats 12 bytes offset 36 - // tangent 4 floats 16 bytes offset 48 - // midtexture 2 floats 8 bytes offset 64 - - // ---RPLE EXTRAS--- - // brightness_G 2 shorts 4 bytes offset 72 - // brightness_B 2 shorts 4 bytes offset 76 - // edgeTex 2 floats 8 bytes offset 80 - private static void initOptiFineShadersVertexPointers() { - int stride; - if (Compat.isFalseTweaksModPresent()) { - stride = VertexAPI.recomputeVertexInfo(18, 4); - } else { - stride = 72; - } - val entityAttrib = 10; - val midTexCoordAttrib = 11; - val tangentAttrib = 12; - - // position 3 floats 12 bytes offset 0 - GL11.glVertexPointer(3, GL11.GL_FLOAT, stride, 0); - GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); - - // texture 2 floats 8 bytes offset 12 - GL11.glTexCoordPointer(2, GL11.GL_FLOAT, stride, 12); - GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); - - // color 4 bytes 4 bytes offset 20 - GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, stride, 20); - GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); - - // brightness 2 shorts 4 bytes offset 24 - if (!Compat.isRPLEModPresent()) { // RPLE sets this up in enableVertexPointersVBO - OpenGlHelper.setClientActiveTexture(OpenGlHelper.lightmapTexUnit); - GL11.glTexCoordPointer(2, GL11.GL_SHORT, stride, 24); - GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); - OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); - } - - // entitydata 3 shorts 6 bytes offset 28 - GL20.glVertexAttribPointer(entityAttrib, 3, GL11.GL_SHORT, false, stride, 28); - GL20.glEnableVertexAttribArray(entityAttrib); - - // normal 3 floats 12 bytes offset 36 - GL11.glNormalPointer(GL11.GL_FLOAT, stride, 36); - GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY); - - // tangent 4 floats 16 bytes offset 48 - GL20.glVertexAttribPointer(tangentAttrib, 4, GL11.GL_FLOAT, false, stride, 48); - GL20.glEnableVertexAttribArray(tangentAttrib); - - // midtexture 2 floats 8 bytes offset 64 - GL13.glClientActiveTexture(GL13.GL_TEXTURE3); - GL11.glTexCoordPointer(2, GL11.GL_FLOAT, stride, 64); - GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); - OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); - - ARBVertexShader.glVertexAttribPointerARB(midTexCoordAttrib, 2, GL11.GL_FLOAT, false, stride, 64); - ARBVertexShader.glEnableVertexAttribArrayARB(midTexCoordAttrib); - - if (Compat.isRPLEModPresent()) { - RPLELightMapUtil.enableVertexPointersVBO(); - ARBVertexShader.glVertexAttribPointerARB(RPLEShaderConstants.edgeTexCoordAttrib, - 2, - GL_FLOAT, - false, - stride, - 80); - ARBVertexShader.glEnableVertexAttribArrayARB(RPLEShaderConstants.edgeTexCoordAttrib); - } + return mem; } public int getStride() { @@ -661,11 +651,13 @@ public void destroy() { glDeleteProgram(shaderProgramsFog[1]); glDeleteProgram(shaderProgramsNoFog[0]); glDeleteProgram(shaderProgramsNoFog[1]); - glDeleteVertexArrays(VAO); - mem.destroy(); + for (val mem: mems) { + mem.destroy(); + } - ChunkMesh.instances = 0; - ChunkMesh.usedRAM = 0; + for (val region: loadedRegionsList) { + region.destroy(); + } } public void onWorldRendererChanged(WorldRenderer wr, WorldRendererChange change) { @@ -693,8 +685,8 @@ public void onWorldRendererPost(WorldRenderer wr, boolean sort) { if (Minecraft.getMinecraft().theWorld.getChunkFromChunkCoords(x, z).isChunkLoaded) { NeoChunk neoChunk = getNeoChunk(x, z); - neoChunk.isSectionVisible[y] = ((IWorldRenderer) wr).isDrawn(); - neoChunk.putChunkMeshes(y, ((IWorldRenderer) wr).getChunkMeshes(), sort); + neoChunk.isSectionVisible[y] = ((NeodymiumWorldRenderer) wr).nd$isDrawn(); + neoChunk.putChunkMeshes(y, ((NeodymiumWorldRenderer) wr).nd$getChunkMeshes(), sort); } } @@ -745,32 +737,60 @@ public void neoChunkChanged(NeoChunk neoChunk) { setMeshVisible(cm, false); } } + uploadMeshToGPU(cm); } } } } + + protected void uploadMeshToGPU(Mesh mesh) { + if (mesh.gpuStatus != GPUStatus.UNSENT || mesh.buffer == null) { + return; + } + boolean sent = false; + GPUMemoryManager mem = null; + val memArr = memMap.get(mesh.pass); + if (memArr != null) + for (int i = 0; i < memArr.size(); i++) { + mem = memArr.get(i); + sent = mem.uploadMesh(mesh); + if (sent) + break; + } + if (!sent) { + try { + mem = initMemoryManager(mesh.pass); + } catch (Exception e) { + ChatUtil.showNeoChatMessage("Could not allocate memory buffer: " + e.getMessage(), ChatUtil.MessageVerbosity.ERROR); + e.printStackTrace(); + Neodymium.renderer.destroyPending = true; + return; + } + mem.uploadMesh(mesh); + } + NeoRegion region = getRegionContaining(mesh.x, mesh.z); + region.getRenderData(mem).getSentMeshes().add(mesh); + mesh.containingRegion = region; + } + protected void setMeshVisible(Mesh mesh, boolean visible) { if (mesh == null) return; if (mesh.visible != visible) { mesh.visible = visible; - - if (mesh.gpuStatus == GPUStatus.UNSENT) { - mem.sendMeshToGPU(mesh); - NeoRegion region = getRegionContaining(mesh.x, mesh.z); - region.getRenderData().getSentMeshes(mesh.pass).add(mesh); - mesh.containingRegion = region; - } } } public void removeMesh(Mesh mesh) { if (mesh == null) return; - mem.deleteMeshFromGPU(mesh); - if (mesh.containingRegion != null) { - mesh.containingRegion.getRenderData().getSentMeshes(mesh.pass).remove(mesh); + val mem = mesh.attachedManager; + if (mem != null) { + mesh.attachedManager.deleteMesh(mesh); + if (mesh.containingRegion != null) { + mesh.containingRegion.getRenderData(mem).getSentMeshes().remove(mesh); + } } setMeshVisible(mesh, false); } @@ -782,11 +802,18 @@ public List getDebugText(boolean statusCommand) { + (statusCommand ? EnumChatFormatting.LIGHT_PURPLE : "") + "Neodymium " + VERSION ); - text.addAll(mem.getDebugText()); text.addAll(Arrays.asList( - "Meshes: " + ChunkMesh.instances + " (" + ChunkMesh.usedRAM / 1024 / 1024 + "MB)", - "Rendered: " + renderedMeshes + " (" + renderedQuads / 1000 + "KQ)" - )); + "Meshes: " + ChunkMesh.instances.get() + " (" + ChunkMesh.usedRAM.get() / 1024 / 1024 + "MB)", + "Rendered: " + renderedMeshesRender + " (" + renderedQuadsRender / 1000 + "KQ)" + )); + if (Compat.isOptiFineShadersEnabled()) { + text.add("Shadow Rendered: " + renderedMeshesShadow + " (" + renderedQuadsShadow / 1000 + "KQ)"); + } + text.add("VRAM buffers:"); + for (int i = 0; i < mems.size(); i++) { + val mem = mems.get(i); + text.addAll(mem.debugText()); + } if (rendererSpeedup > 0) { text.add(EnumChatFormatting.YELLOW + "(!) Renderer speedup active"); } diff --git a/src/main/java/makamys/neodymium/renderer/compat/RenderUtil.java b/src/main/java/makamys/neodymium/renderer/compat/RenderUtil.java index 70996eb..77bf364 100644 --- a/src/main/java/makamys/neodymium/renderer/compat/RenderUtil.java +++ b/src/main/java/makamys/neodymium/renderer/compat/RenderUtil.java @@ -1,18 +1,34 @@ package makamys.neodymium.renderer.compat; import makamys.neodymium.renderer.ChunkMesh; -import makamys.neodymium.renderer.MeshQuad; import makamys.neodymium.renderer.NeoRenderer; import makamys.neodymium.renderer.attribs.AttributeSet; import makamys.neodymium.util.BufferWriter; public interface RenderUtil { - void readMeshQuad(MeshQuad meshQuad, int[] rawBuffer, int tessellatorVertexSize, int offset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags); + int QUAD_OFFSET_XPOS = 0; + int QUAD_OFFSET_YPOS = 1; + int QUAD_OFFSET_ZPOS = 2; + + void readMeshQuad(int[] tessBuffer, int tessOffset, int[] quadBuffer, int quadOffset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags); /** * @implSpec These needs to be kept in sync with the attributes in {@link NeoRenderer#init()} */ - void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expectedStride); + void writeMeshQuadToBuffer(int[] meshQuadBuffer, int quadOffset, BufferWriter out, int expectedStride); + + int vertexSizeInTessellator(); + + int vertexSizeInQuadBuffer(); + + // Include the quad normal + default int quadSize() { + return vertexSizeInQuadBuffer() * 4 + 1; + } void initVertexAttributes(AttributeSet attributes); + + default void applyVertexAttributes(AttributeSet attributes) { + attributes.enable(); + } } diff --git a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilRPLE.java b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilRPLE.java index fb27fee..29b9481 100644 --- a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilRPLE.java +++ b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilRPLE.java @@ -2,6 +2,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import lombok.val; import makamys.neodymium.config.Config; import makamys.neodymium.renderer.ChunkMesh; import makamys.neodymium.renderer.MeshQuad; @@ -20,60 +21,66 @@ public class RenderUtilRPLE implements RenderUtil { public static final RenderUtilRPLE INSTANCE = new RenderUtilRPLE(); + public static final int QUAD_OFFSET_U = 3; + public static final int QUAD_OFFSET_V = 4; + public static final int QUAD_OFFSET_C = 5; + public static final int QUAD_OFFSET_BR = 6; + public static final int QUAD_OFFSET_BG = 7; + public static final int QUAD_OFFSET_BB = 8; + @Override - public void readMeshQuad(MeshQuad meshQuad, int[] rawBuffer, int tessellatorVertexSize, int offset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { + public void readMeshQuad(int[] tessBuffer, int tessOffset, int[] quadBuffer, int quadOffset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { + val tessVertexSize = vertexSizeInTessellator(); + val quadVertexSize = vertexSizeInQuadBuffer(); + int vertices = drawMode == GL11.GL_TRIANGLES ? 3 : 4; for(int vi = 0; vi < vertices; vi++) { - int i = offset + vi * tessellatorVertexSize; + int tI = tessOffset + vi * tessVertexSize; + int qI = quadOffset + vi * quadVertexSize; - meshQuad.xs[vi] = Float.intBitsToFloat(rawBuffer[i]) + offsetX; - meshQuad.ys[vi] = Float.intBitsToFloat(rawBuffer[i + 1]) + offsetY; - meshQuad.zs[vi] = Float.intBitsToFloat(rawBuffer[i + 2]) + offsetZ; + quadBuffer[qI + QUAD_OFFSET_XPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI]) + offsetX); + quadBuffer[qI + QUAD_OFFSET_YPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 1]) + offsetY); + quadBuffer[qI + QUAD_OFFSET_ZPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 2]) + offsetZ); - meshQuad.us[vi] = Float.intBitsToFloat(rawBuffer[i + 3]); - meshQuad.vs[vi] = Float.intBitsToFloat(rawBuffer[i + 4]); + quadBuffer[qI + QUAD_OFFSET_U] = tessBuffer[tI + 3]; + quadBuffer[qI + QUAD_OFFSET_V] = tessBuffer[tI + 4]; - meshQuad.cs[vi] = flags.hasColor ? rawBuffer[i + 5] : DEFAULT_COLOR; + quadBuffer[qI + QUAD_OFFSET_C] = flags.hasColor ? tessBuffer[tI + 5] : DEFAULT_COLOR; // TODO normals? if (flags.hasBrightness) { - meshQuad.bs[vi] = rawBuffer[i + 7]; - meshQuad.bsG[vi] = rawBuffer[i + 8]; - meshQuad.bsB[vi] = rawBuffer[i + 9]; + quadBuffer[qI + QUAD_OFFSET_BR] = tessBuffer[tI + 7]; + quadBuffer[qI + QUAD_OFFSET_BG] = tessBuffer[tI + 8]; + quadBuffer[qI + QUAD_OFFSET_BB] = tessBuffer[tI + 9]; } else { - meshQuad.bs[vi] = DEFAULT_BRIGHTNESS; - meshQuad.bsG[vi] = DEFAULT_BRIGHTNESS; - meshQuad.bsB[vi] = DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_BR] = DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_BG] = DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_BB] = DEFAULT_BRIGHTNESS; } } + if(vertices == 3) { // Quadrangulate! - meshQuad.xs[3] = meshQuad.xs[2]; - meshQuad.ys[3] = meshQuad.ys[2]; - meshQuad.zs[3] = meshQuad.zs[2]; + int q2 = quadOffset + 2 * quadVertexSize; + int q3 = quadOffset + 3 * quadVertexSize; - meshQuad.us[3] = meshQuad.us[2]; - meshQuad.vs[3] = meshQuad.vs[2]; - - meshQuad.cs[3] = meshQuad.cs[2]; - - meshQuad.bs[3] = meshQuad.bs[2]; - meshQuad.bsG[3] = meshQuad.bsG[2]; - meshQuad.bsB[3] = meshQuad.bsB[2]; + System.arraycopy(quadBuffer, q2, quadBuffer, q3, quadVertexSize); } } @Override - public void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expectedStride) { + public void writeMeshQuadToBuffer(int[] meshQuadBuffer, int quadOffset, BufferWriter out, int expectedStride) { + val vertexSize = vertexSizeInQuadBuffer(); for(int vi = 0; vi < 4; vi++) { - out.writeFloat(meshQuad.xs[vi]); - out.writeFloat(meshQuad.ys[vi]); - out.writeFloat(meshQuad.zs[vi]); + int offset = quadOffset + vi * vertexSize; + out.writeFloat(Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_XPOS])); + out.writeFloat(Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_YPOS])); + out.writeFloat(Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_ZPOS])); - float u = meshQuad.us[vi]; - float v = meshQuad.vs[vi]; + float u = Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_U]); + float v = Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_V]); if(Config.shortUV) { out.writeShort((short)(Math.round(u * 32768f))); @@ -83,16 +90,28 @@ public void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expec out.writeFloat(v); } - out.writeInt(meshQuad.cs[vi]); + out.writeInt(meshQuadBuffer[offset + QUAD_OFFSET_C]); - out.writeInt(meshQuad.bs[vi]); - out.writeInt(meshQuad.bsG[vi]); - out.writeInt(meshQuad.bsB[vi]); + out.writeInt(meshQuadBuffer[offset + QUAD_OFFSET_BR]); + out.writeInt(meshQuadBuffer[offset + QUAD_OFFSET_BG]); + out.writeInt(meshQuadBuffer[offset + QUAD_OFFSET_BB]); assert out.position() % expectedStride == 0; } } + @Override + public int vertexSizeInTessellator() { + // pos + uv + color + normal + brightnessRGB + + return 3 + 2 + 1 + 1 + 3 + 2; + } + + @Override + public int vertexSizeInQuadBuffer() { + // pos + uv + color + brightnessRGB + return 3 + 2 + 1 + 3; + } + @Override public void initVertexAttributes(AttributeSet attributes) { attributes.addAttribute("POS", 3, 4, GL_FLOAT); diff --git a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaderRPLE.java b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaderRPLE.java index 731bfd6..3e184c6 100644 --- a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaderRPLE.java +++ b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaderRPLE.java @@ -1,143 +1,123 @@ package makamys.neodymium.renderer.compat; +import com.falsepattern.rple.api.client.RPLELightMapUtil; +import com.falsepattern.rple.api.client.RPLEShaderConstants; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import lombok.val; import makamys.neodymium.renderer.ChunkMesh; -import makamys.neodymium.renderer.MeshQuad; import makamys.neodymium.renderer.attribs.AttributeSet; import makamys.neodymium.util.BufferWriter; +import net.minecraft.client.renderer.OpenGlHelper; +import org.lwjgl.opengl.ARBVertexShader; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL20; import static makamys.neodymium.renderer.MeshQuad.DEFAULT_BRIGHTNESS; import static makamys.neodymium.renderer.MeshQuad.DEFAULT_COLOR; -import static org.lwjgl.opengl.GL11.GL_FLOAT; -import static org.lwjgl.opengl.GL11.GL_SHORT; -import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; -import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL13.*; +import static org.lwjgl.opengl.GL20.*; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class RenderUtilShaderRPLE implements RenderUtil { public static final RenderUtilShaderRPLE INSTANCE = new RenderUtilShaderRPLE(); - @Override - public void readMeshQuad(MeshQuad meshQuad, int[] rawBuffer, int tessellatorVertexSize, int offset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { - int vertices = drawMode == GL11.GL_TRIANGLES ? 3 : 4; - for(int vi = 0; vi < vertices; vi++) { - int i = offset + vi * tessellatorVertexSize; - - meshQuad.xs[vi] = Float.intBitsToFloat(rawBuffer[i]) + offsetX; - meshQuad.ys[vi] = Float.intBitsToFloat(rawBuffer[i + 1]) + offsetY; - meshQuad.zs[vi] = Float.intBitsToFloat(rawBuffer[i + 2]) + offsetZ; + public static final int QUAD_OFFSET_U = 3; + public static final int QUAD_OFFSET_V = 4; + public static final int QUAD_OFFSET_C = 5; + public static final int QUAD_OFFSET_BR = 6; + public static final int QUAD_OFFSET_E1 = 7; + public static final int QUAD_OFFSET_E2 = 8; + public static final int QUAD_OFFSET_XN = 9; + public static final int QUAD_OFFSET_YN = 10; + public static final int QUAD_OFFSET_ZN = 11; + public static final int QUAD_OFFSET_XT = 12; + public static final int QUAD_OFFSET_YT = 13; + public static final int QUAD_OFFSET_ZT = 14; + public static final int QUAD_OFFSET_WT = 15; + public static final int QUAD_OFFSET_UM = 16; + public static final int QUAD_OFFSET_VM = 17; + public static final int QUAD_OFFSET_BG = 18; + public static final int QUAD_OFFSET_BB = 19; + public static final int QUAD_OFFSET_UE = 20; + public static final int QUAD_OFFSET_VE = 21; + + public static final int QUAD_OFFSET_OPTIFINE_START = QUAD_OFFSET_E1; + public static final int QUAD_OFFSET_OPTIFINE_END = QUAD_OFFSET_VM; + public static final int QUAD_OFFSET_OPTIFINE_COUNT = QUAD_OFFSET_OPTIFINE_END - QUAD_OFFSET_OPTIFINE_START + 1; - meshQuad.us[vi] = Float.intBitsToFloat(rawBuffer[i + 3]); - meshQuad.vs[vi] = Float.intBitsToFloat(rawBuffer[i + 4]); + @Override + public void readMeshQuad(int[] tessBuffer, int tessOffset, int[] quadBuffer, int quadOffset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { + val tessVertexSize = vertexSizeInTessellator(); + val quadVertexSize = vertexSizeInQuadBuffer(); - meshQuad.cs[vi] = flags.hasColor ? rawBuffer[i + 5] : DEFAULT_COLOR; + final int vertices = drawMode == GL_TRIANGLES ? 3 : 4; + for (int vi = 0; vi < vertices; vi++) { + final int tI = tessOffset + vi * tessVertexSize; + final int qI = quadOffset + vi * quadVertexSize; - meshQuad.bs[vi] = flags.hasBrightness ? rawBuffer[i + 6] : DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_XPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI]) + offsetX); + quadBuffer[qI + QUAD_OFFSET_YPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 1]) + offsetY); + quadBuffer[qI + QUAD_OFFSET_ZPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 2]) + offsetZ); - meshQuad.e1[vi] = rawBuffer[i + 7]; - meshQuad.e2[vi] = rawBuffer[i + 8]; + quadBuffer[qI + QUAD_OFFSET_U] = tessBuffer[tI + 3]; + quadBuffer[qI + QUAD_OFFSET_V] = tessBuffer[tI + 4]; - meshQuad.xn[vi] = Float.intBitsToFloat(rawBuffer[i + 9]); - meshQuad.yn[vi] = Float.intBitsToFloat(rawBuffer[i + 10]); - meshQuad.zn[vi] = Float.intBitsToFloat(rawBuffer[i + 11]); + quadBuffer[qI + QUAD_OFFSET_C] = flags.hasColor ? tessBuffer[tI + 5] : DEFAULT_COLOR; - meshQuad.xt[vi] = Float.intBitsToFloat(rawBuffer[i + 12]); - meshQuad.yt[vi] = Float.intBitsToFloat(rawBuffer[i + 13]); - meshQuad.zt[vi] = Float.intBitsToFloat(rawBuffer[i + 14]); - meshQuad.wt[vi] = Float.intBitsToFloat(rawBuffer[i + 15]); + quadBuffer[qI + QUAD_OFFSET_BR] = flags.hasBrightness ? tessBuffer[tI + 6] : DEFAULT_BRIGHTNESS; - meshQuad.um[vi] = Float.intBitsToFloat(rawBuffer[i + 16]); - meshQuad.vm[vi] = Float.intBitsToFloat(rawBuffer[i + 17]); + System.arraycopy(tessBuffer, tI + 7, quadBuffer, qI + QUAD_OFFSET_OPTIFINE_START, QUAD_OFFSET_OPTIFINE_COUNT); if (flags.hasBrightness) { - meshQuad.bsG[vi] = rawBuffer[i + 18]; - meshQuad.bsB[vi] = rawBuffer[i + 19]; + quadBuffer[qI + QUAD_OFFSET_BG] = tessBuffer[tI + 18]; + quadBuffer[qI + QUAD_OFFSET_BB] = tessBuffer[tI + 19]; } else { - meshQuad.bsG[vi] = DEFAULT_BRIGHTNESS; - meshQuad.bsB[vi] = DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_BG] = DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_BB] = DEFAULT_BRIGHTNESS; } - meshQuad.ue[vi] = Float.intBitsToFloat(rawBuffer[i + 20]); - meshQuad.ve[vi] = Float.intBitsToFloat(rawBuffer[i + 21]); + quadBuffer[qI + QUAD_OFFSET_UE] = tessBuffer[tI + 20]; + quadBuffer[qI + QUAD_OFFSET_VE] = tessBuffer[tI + 21]; } - if(vertices == 3) { - // Quadrangulate! - meshQuad.xs[3] = meshQuad.xs[2]; - meshQuad.ys[3] = meshQuad.ys[2]; - meshQuad.zs[3] = meshQuad.zs[2]; - - meshQuad.us[3] = meshQuad.us[2]; - meshQuad.vs[3] = meshQuad.vs[2]; - - meshQuad.cs[3] = meshQuad.cs[2]; - - meshQuad.bs[3] = meshQuad.bs[2]; - - meshQuad.e1[3] = meshQuad.e1[2]; - meshQuad.e2[3] = meshQuad.e2[2]; - meshQuad.xn[3] = meshQuad.xn[2]; - meshQuad.yn[3] = meshQuad.yn[2]; - meshQuad.zn[3] = meshQuad.zn[2]; - - meshQuad.xt[3] = meshQuad.xt[2]; - meshQuad.yt[3] = meshQuad.yt[2]; - meshQuad.zt[3] = meshQuad.zt[2]; - meshQuad.wt[3] = meshQuad.wt[2]; - - meshQuad.um[3] = meshQuad.um[2]; - meshQuad.vm[3] = meshQuad.vm[2]; - - meshQuad.bsG[3] = meshQuad.bsG[2]; - meshQuad.bsB[3] = meshQuad.bsB[2]; + if (vertices == 3) { + // Quadrangulate! + final int q2 = quadOffset + 2 * quadVertexSize; + final int q3 = quadOffset + 3 * quadVertexSize; - meshQuad.ue[3] = meshQuad.ue[2]; - meshQuad.ve[3] = meshQuad.ve[2]; + System.arraycopy(quadBuffer, q2, quadBuffer, q3, quadVertexSize); } } @Override - public void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expectedStride) { - for(int vi = 0; vi < 4; vi++) { - out.writeFloat(meshQuad.xs[vi]); - out.writeFloat(meshQuad.ys[vi]); - out.writeFloat(meshQuad.zs[vi]); - - out.writeFloat(meshQuad.us[vi]); - out.writeFloat(meshQuad.vs[vi]); - - out.writeInt(meshQuad.cs[vi]); - - out.writeInt(meshQuad.bs[vi]); - - out.writeInt(meshQuad.e1[vi]); - out.writeInt(meshQuad.e2[vi]); - - out.writeFloat(meshQuad.xn[vi]); - out.writeFloat(meshQuad.yn[vi]); - out.writeFloat(meshQuad.zn[vi]); - - out.writeFloat(meshQuad.xt[vi]); - out.writeFloat(meshQuad.yt[vi]); - out.writeFloat(meshQuad.zt[vi]); - out.writeFloat(meshQuad.wt[vi]); - - out.writeFloat(meshQuad.um[vi]); - out.writeFloat(meshQuad.vm[vi]); - - out.writeInt(meshQuad.bsG[vi]); - out.writeInt(meshQuad.bsB[vi]); - - out.writeFloat(meshQuad.ue[vi]); - out.writeFloat(meshQuad.ve[vi]); + public void writeMeshQuadToBuffer(int[] meshQuadBuffer, int quadOffset, BufferWriter out, int expectedStride) { + val vertexSize = vertexSizeInQuadBuffer(); + for (int vi = 0; vi < 4; vi++) { + final int offset = quadOffset + vi * vertexSize; + for (int i = 0; i < vertexSize; i++) { + out.writeInt(meshQuadBuffer[offset + i]); + } assert out.position() % expectedStride == 0; } } + @Override + public int vertexSizeInTessellator() { + // pos + uv + color + brightnessR + entityData + normal + tangent + midtexture + brightnessGB + edgeTexture + return 3 + 2 + 1 + 1 + 2 + 3 + 4 + 2 + 2 + 2; + } + + @Override + public int vertexSizeInQuadBuffer() { + // pos + uv + color + brightness + entityData + normal + tangent + midtexture + brightnessGB + edgeTexture + return 3 + 2 + 1 + 1 + 2 + 3 + 4 + 2 + 2 + 2; + } + @Override public void initVertexAttributes(AttributeSet attributes) { attributes.addAttribute("POS", 3, 4, GL_FLOAT); @@ -153,4 +133,72 @@ public void initVertexAttributes(AttributeSet attributes) { attributes.addAttribute("BRIGHTNESS_BLUE", 2, 2, GL_SHORT); attributes.addAttribute("EDGE_TEX", 2, 4, GL_FLOAT); } + + /** + * TODO: This format is nice, we should have it in the docs too! + * position 3 floats 12 bytes offset 0 + * texture 2 floats 8 bytes offset 12 + * color 4 bytes 4 bytes offset 20 + * brightness_R 2 shorts 4 bytes offset 24 + * entitydata 3 shorts 6 bytes offset 28 + * [padding] -------- 2 bytes offset 34 + * normal 3 floats 12 bytes offset 36 + * tangent 4 floats 16 bytes offset 48 + * midtexture 2 floats 8 bytes offset 64 + * brightness_G 2 shorts 4 bytes offset 72 + * brightness_B 2 shorts 4 bytes offset 76 + * edgeTex 2 floats 8 bytes offset 80 + * + * @param attributes Configured Attributes + */ + @Override + public void applyVertexAttributes(AttributeSet attributes) { + val stride = attributes.stride(); + + val entityAttrib = 10; + val midTexCoordAttrib = 11; + val tangentAttrib = 12; + + // position 3 floats 12 bytes offset 0 + glVertexPointer(3, GL_FLOAT, stride, 0); + glEnableClientState(GL_VERTEX_ARRAY); + + // texture 2 floats 8 bytes offset 12 + glTexCoordPointer(2, GL_FLOAT, stride, 12); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // color 4 bytes 4 bytes offset 20 + glColorPointer(4, GL_UNSIGNED_BYTE, stride, 20); + glEnableClientState(GL_COLOR_ARRAY); + + // entitydata 3 shorts 6 bytes offset 28 + glVertexAttribPointer(entityAttrib, 3, GL_SHORT, false, stride, 28); + glEnableVertexAttribArray(entityAttrib); + + // normal 3 floats 12 bytes offset 36 + glNormalPointer(GL_FLOAT, stride, 36); + glEnableClientState(GL_NORMAL_ARRAY); + + // tangent 4 floats 16 bytes offset 48 + glVertexAttribPointer(tangentAttrib, 4, GL_FLOAT, false, stride, 48); + glEnableVertexAttribArray(tangentAttrib); + + // midtexture 2 floats 8 bytes offset 64 + glClientActiveTexture(GL_TEXTURE3); + glTexCoordPointer(2, GL_FLOAT, stride, 64); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); + + ARBVertexShader.glVertexAttribPointerARB(midTexCoordAttrib, 2, GL_FLOAT, false, stride, 64); + ARBVertexShader.glEnableVertexAttribArrayARB(midTexCoordAttrib); + + RPLELightMapUtil.enableVertexPointersVBO(); + ARBVertexShader.glVertexAttribPointerARB(RPLEShaderConstants.edgeTexCoordAttrib, + 2, + GL_FLOAT, + false, + stride, + 80); + ARBVertexShader.glEnableVertexAttribArrayARB(RPLEShaderConstants.edgeTexCoordAttrib); + } } diff --git a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaders.java b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaders.java index b621e2c..5afe997 100644 --- a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaders.java +++ b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilShaders.java @@ -1,116 +1,116 @@ package makamys.neodymium.renderer.compat; +import com.falsepattern.falsetweaks.api.triangulator.VertexAPI; +import com.falsepattern.rple.api.client.RPLELightMapUtil; +import com.falsepattern.rple.api.client.RPLEShaderConstants; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import lombok.val; +import makamys.neodymium.Compat; +import makamys.neodymium.config.Config; import makamys.neodymium.renderer.ChunkMesh; import makamys.neodymium.renderer.MeshQuad; import makamys.neodymium.renderer.attribs.AttributeSet; import makamys.neodymium.util.BufferWriter; +import net.minecraft.client.renderer.OpenGlHelper; +import org.lwjgl.opengl.ARBVertexShader; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL20; import static makamys.neodymium.renderer.MeshQuad.DEFAULT_BRIGHTNESS; import static makamys.neodymium.renderer.MeshQuad.DEFAULT_COLOR; +import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.GL_FLOAT; import static org.lwjgl.opengl.GL11.GL_SHORT; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL13.*; +import static org.lwjgl.opengl.GL20.*; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class RenderUtilShaders implements RenderUtil { public static final RenderUtilShaders INSTANCE = new RenderUtilShaders(); + public static final int QUAD_OFFSET_U = 3; + public static final int QUAD_OFFSET_V = 4; + public static final int QUAD_OFFSET_C = 5; + public static final int QUAD_OFFSET_B = 6; + public static final int QUAD_OFFSET_E1 = 7; + public static final int QUAD_OFFSET_E2 = 8; + public static final int QUAD_OFFSET_XN = 9; + public static final int QUAD_OFFSET_YN = 10; + public static final int QUAD_OFFSET_ZN = 11; + public static final int QUAD_OFFSET_XT = 12; + public static final int QUAD_OFFSET_YT = 13; + public static final int QUAD_OFFSET_ZT = 14; + public static final int QUAD_OFFSET_WT = 15; + public static final int QUAD_OFFSET_UM = 16; + public static final int QUAD_OFFSET_VM = 17; + + public static final int QUAD_OFFSET_OPTIFINE_START = QUAD_OFFSET_E1; + public static final int QUAD_OFFSET_OPTIFINE_END = QUAD_OFFSET_VM; + public static final int QUAD_OFFSET_OPTIFINE_COUNT = QUAD_OFFSET_OPTIFINE_END - QUAD_OFFSET_OPTIFINE_START + 1; + @Override - public void readMeshQuad(MeshQuad meshQuad, int[] rawBuffer, int tessellatorVertexSize, int offset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { - int vertices = drawMode == GL11.GL_TRIANGLES ? 3 : 4; - for (int vi = 0; vi < vertices; vi++) { - int i = offset + vi * tessellatorVertexSize; - - meshQuad.xs[vi] = Float.intBitsToFloat(rawBuffer[i]) + offsetX; - meshQuad.ys[vi] = Float.intBitsToFloat(rawBuffer[i + 1]) + offsetY; - meshQuad.zs[vi] = Float.intBitsToFloat(rawBuffer[i + 2]) + offsetZ; - - meshQuad.us[vi] = Float.intBitsToFloat(rawBuffer[i + 3]); - meshQuad.vs[vi] = Float.intBitsToFloat(rawBuffer[i + 4]); - - meshQuad.cs[vi] = flags.hasColor ? rawBuffer[i + 5] : DEFAULT_COLOR; - - meshQuad.bs[vi] = flags.hasBrightness ? rawBuffer[i + 6] : DEFAULT_BRIGHTNESS; - meshQuad.e1[vi] = rawBuffer[i + 7]; - meshQuad.e2[vi] = rawBuffer[i + 8]; - meshQuad.xn[vi] = Float.intBitsToFloat(rawBuffer[i + 9]); - meshQuad.yn[vi] = Float.intBitsToFloat(rawBuffer[i + 10]); - meshQuad.zn[vi] = Float.intBitsToFloat(rawBuffer[i + 11]); - meshQuad.xt[vi] = Float.intBitsToFloat(rawBuffer[i + 12]); - meshQuad.yt[vi] = Float.intBitsToFloat(rawBuffer[i + 13]); - meshQuad.zt[vi] = Float.intBitsToFloat(rawBuffer[i + 14]); - meshQuad.wt[vi] = Float.intBitsToFloat(rawBuffer[i + 15]); - meshQuad.um[vi] = Float.intBitsToFloat(rawBuffer[i + 16]); - meshQuad.vm[vi] = Float.intBitsToFloat(rawBuffer[i + 17]); - } + public void readMeshQuad(int[] tessBuffer, int tessOffset, int[] quadBuffer, int quadOffset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { + val tessVertexSize = vertexSizeInTessellator(); + val quadVertexSize = vertexSizeInQuadBuffer(); - if (vertices == 3) { - // Quadrangulate! - meshQuad.xs[3] = meshQuad.xs[2]; - meshQuad.ys[3] = meshQuad.ys[2]; - meshQuad.zs[3] = meshQuad.zs[2]; + int vertices = drawMode == GL_TRIANGLES ? 3 : 4; + for(int vi = 0; vi < vertices; vi++) { + int tI = tessOffset + vi * tessVertexSize; + int qI = quadOffset + vi * quadVertexSize; - meshQuad.us[3] = meshQuad.us[2]; - meshQuad.vs[3] = meshQuad.vs[2]; + quadBuffer[qI + QUAD_OFFSET_XPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI]) + offsetX); + quadBuffer[qI + QUAD_OFFSET_YPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 1]) + offsetY); + quadBuffer[qI + QUAD_OFFSET_ZPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 2]) + offsetZ); - meshQuad.cs[3] = meshQuad.cs[2]; + quadBuffer[qI + QUAD_OFFSET_U] = tessBuffer[tI + 3]; + quadBuffer[qI + QUAD_OFFSET_V] = tessBuffer[tI + 4]; - meshQuad.bs[3] = meshQuad.bs[2]; + quadBuffer[qI + QUAD_OFFSET_C] = flags.hasColor ? tessBuffer[tI + 5] : DEFAULT_COLOR; - meshQuad.e1[3] = meshQuad.e1[2]; - meshQuad.e2[3] = meshQuad.e2[2]; + quadBuffer[qI + QUAD_OFFSET_B] = flags.hasBrightness ? tessBuffer[tI + 6] : DEFAULT_BRIGHTNESS; + + System.arraycopy(tessBuffer, tI + 7, quadBuffer, qI + QUAD_OFFSET_OPTIFINE_START, QUAD_OFFSET_OPTIFINE_COUNT); + } - meshQuad.xn[3] = meshQuad.xn[2]; - meshQuad.yn[3] = meshQuad.yn[2]; - meshQuad.zn[3] = meshQuad.zn[2]; - meshQuad.xt[3] = meshQuad.xt[2]; - meshQuad.yt[3] = meshQuad.yt[2]; - meshQuad.zt[3] = meshQuad.zt[2]; - meshQuad.wt[3] = meshQuad.wt[2]; + if(vertices == 3) { + // Quadrangulate! + int q2 = quadOffset + 2 * quadVertexSize; + int q3 = quadOffset + 3 * quadVertexSize; - meshQuad.um[3] = meshQuad.um[2]; - meshQuad.vm[3] = meshQuad.vm[2]; + System.arraycopy(quadBuffer, q2, quadBuffer, q3, quadVertexSize); } } @Override - public void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expectedStride) { + public void writeMeshQuadToBuffer(int[] meshQuadBuffer, int quadOffset, BufferWriter out, int expectedStride) { + val vertexSize = vertexSizeInQuadBuffer(); for(int vi = 0; vi < 4; vi++) { - out.writeFloat(meshQuad.xs[vi]); - out.writeFloat(meshQuad.ys[vi]); - out.writeFloat(meshQuad.zs[vi]); - - out.writeFloat(meshQuad.us[vi]); - out.writeFloat(meshQuad.vs[vi]); - - out.writeInt(meshQuad.cs[vi]); - - out.writeInt(meshQuad.bs[vi]); - - out.writeInt(meshQuad.e1[vi]); - out.writeInt(meshQuad.e2[vi]); - - out.writeFloat(meshQuad.xn[vi]); - out.writeFloat(meshQuad.yn[vi]); - out.writeFloat(meshQuad.zn[vi]); - - out.writeFloat(meshQuad.xt[vi]); - out.writeFloat(meshQuad.yt[vi]); - out.writeFloat(meshQuad.zt[vi]); - out.writeFloat(meshQuad.wt[vi]); - - out.writeFloat(meshQuad.um[vi]); - out.writeFloat(meshQuad.vm[vi]); + int offset = quadOffset + vi * vertexSize; + for (int i = 0; i < vertexSize; i++) { + out.writeInt(meshQuadBuffer[offset + i]); + } assert out.position() % expectedStride == 0; } } + @Override + public int vertexSizeInTessellator() { + // pos + uv + color + brightness + entityData + normal + tangent + midtexture + return 3 + 2 + 1 + 1 + 2 + 3 + 4 + 2; + } + + @Override + public int vertexSizeInQuadBuffer() { + // pos + uv + color + brightness + entityData + normal + tangent + midtexture + return 3 + 2 + 1 + 1 + 2 + 3 + 4 + 2; + } + @Override public void initVertexAttributes(AttributeSet attributes) { attributes.addAttribute("POS", 3, 4, GL_FLOAT); @@ -123,4 +123,67 @@ public void initVertexAttributes(AttributeSet attributes) { attributes.addAttribute("TANGENT", 4, 4, GL_FLOAT); attributes.addAttribute("MIDTEXTURE", 2, 4, GL_FLOAT); } + + + /** + * TODO: This format is nice, we should have it in the docs too! + * position 3 floats 12 bytes offset 0 + * texture 2 floats 8 bytes offset 12 + * color 4 bytes 4 bytes offset 20 + * brightness 2 shorts 4 bytes offset 24 + * entitydata 3 shorts 6 bytes offset 28 + * [padding] -------- 2 bytes offset 34 + * normal 3 floats 12 bytes offset 36 + * tangent 4 floats 16 bytes offset 48 + * midtexture 2 floats 8 bytes offset 64 + * + * @param attributes Configured Attributes + */ + @Override + public void applyVertexAttributes(AttributeSet attributes) { + val stride = attributes.stride(); + + val entityAttrib = 10; + val midTexCoordAttrib = 11; + val tangentAttrib = 12; + + // position 3 floats 12 bytes offset 0 + glVertexPointer(3, GL_FLOAT, stride, 0); + glEnableClientState(GL_VERTEX_ARRAY); + + // texture 2 floats 8 bytes offset 12 + glTexCoordPointer(2, GL_FLOAT, stride, 12); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // color 4 bytes 4 bytes offset 20 + glColorPointer(4, GL_UNSIGNED_BYTE, stride, 20); + glEnableClientState(GL_COLOR_ARRAY); + + // brightness 2 shorts 4 bytes offset 24 + OpenGlHelper.setClientActiveTexture(OpenGlHelper.lightmapTexUnit); + glTexCoordPointer(2, GL_SHORT, stride, 24); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); + + // entitydata 3 shorts 6 bytes offset 28 + glVertexAttribPointer(entityAttrib, 3, GL_SHORT, false, stride, 28); + glEnableVertexAttribArray(entityAttrib); + + // normal 3 floats 12 bytes offset 36 + glNormalPointer(GL_FLOAT, stride, 36); + glEnableClientState(GL_NORMAL_ARRAY); + + // tangent 4 floats 16 bytes offset 48 + glVertexAttribPointer(tangentAttrib, 4, GL_FLOAT, false, stride, 48); + glEnableVertexAttribArray(tangentAttrib); + + // midtexture 2 floats 8 bytes offset 64 + glClientActiveTexture(GL_TEXTURE3); + glTexCoordPointer(2, GL_FLOAT, stride, 64); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); + + ARBVertexShader.glVertexAttribPointerARB(midTexCoordAttrib, 2, GL_FLOAT, false, stride, 64); + ARBVertexShader.glEnableVertexAttribArrayARB(midTexCoordAttrib); + } } diff --git a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilVanilla.java b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilVanilla.java index 46f5a2a..9ad14cb 100644 --- a/src/main/java/makamys/neodymium/renderer/compat/RenderUtilVanilla.java +++ b/src/main/java/makamys/neodymium/renderer/compat/RenderUtilVanilla.java @@ -2,6 +2,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import lombok.val; import makamys.neodymium.config.Config; import makamys.neodymium.renderer.ChunkMesh; import makamys.neodymium.renderer.MeshQuad; @@ -20,51 +21,68 @@ public class RenderUtilVanilla implements RenderUtil { public static final RenderUtilVanilla INSTANCE = new RenderUtilVanilla(); + public static final int QUAD_OFFSET_U = 3; + public static final int QUAD_OFFSET_V = 4; + public static final int QUAD_OFFSET_C = 5; + public static final int QUAD_OFFSET_B = 6; + @Override - public void readMeshQuad(MeshQuad meshQuad, int[] rawBuffer, int tessellatorVertexSize, int offset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { - //No RPLE or Shaders + public void readMeshQuad(int[] tessBuffer, int tessOffset, int[] quadBuffer, int quadOffset, float offsetX, float offsetY, float offsetZ, int drawMode, ChunkMesh.Flags flags) { + val tessVertexSize = vertexSizeInTessellator(); + val quadVertexSize = vertexSizeInQuadBuffer(); + int vertices = drawMode == GL11.GL_TRIANGLES ? 3 : 4; for(int vi = 0; vi < vertices; vi++) { - int i = offset + vi * tessellatorVertexSize; + int tI = tessOffset + vi * tessVertexSize; + int qI = quadOffset + vi * quadVertexSize; - meshQuad.xs[vi] = Float.intBitsToFloat(rawBuffer[i]) + offsetX; - meshQuad.ys[vi] = Float.intBitsToFloat(rawBuffer[i + 1]) + offsetY; - meshQuad.zs[vi] = Float.intBitsToFloat(rawBuffer[i + 2]) + offsetZ; + quadBuffer[qI + QUAD_OFFSET_XPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI]) + offsetX); + quadBuffer[qI + QUAD_OFFSET_YPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 1]) + offsetY); + quadBuffer[qI + QUAD_OFFSET_ZPOS] = Float.floatToRawIntBits(Float.intBitsToFloat(tessBuffer[tI + 2]) + offsetZ); - meshQuad.us[vi] = Float.intBitsToFloat(rawBuffer[i + 3]); - meshQuad.vs[vi] = Float.intBitsToFloat(rawBuffer[i + 4]); + quadBuffer[qI + QUAD_OFFSET_U] = tessBuffer[tI + 3]; + quadBuffer[qI + QUAD_OFFSET_V] = tessBuffer[tI + 4]; - meshQuad.cs[vi] = flags.hasColor ? rawBuffer[i + 5] : DEFAULT_COLOR; + quadBuffer[qI + QUAD_OFFSET_C] = flags.hasColor ? tessBuffer[tI + 5] : DEFAULT_COLOR; // TODO normals? - meshQuad.bs[vi] = flags.hasBrightness ? rawBuffer[i + 7] : DEFAULT_BRIGHTNESS; + quadBuffer[qI + QUAD_OFFSET_B] = flags.hasBrightness ? tessBuffer[tI + 7] : DEFAULT_BRIGHTNESS; } + if(vertices == 3) { // Quadrangulate! - meshQuad.xs[3] = meshQuad.xs[2]; - meshQuad.ys[3] = meshQuad.ys[2]; - meshQuad.zs[3] = meshQuad.zs[2]; + int q2 = quadOffset + 2 * quadVertexSize; + int q3 = quadOffset + 3 * quadVertexSize; - meshQuad.us[3] = meshQuad.us[2]; - meshQuad.vs[3] = meshQuad.vs[2]; + System.arraycopy(quadBuffer, q2, quadBuffer, q3, quadVertexSize); + } + } - meshQuad.cs[3] = meshQuad.cs[2]; + @Override + public int vertexSizeInTessellator() { + // pos + uv + color + normal + brightness + return 3 + 2 + 1 + 1 + 1; + } - meshQuad.bs[3] = meshQuad.bs[2]; - } + @Override + public int vertexSizeInQuadBuffer() { + // pos + uv + color + brightness; + return 3 + 2 + 1 + 1; } @Override - public void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expectedStride) { + public void writeMeshQuadToBuffer(int[] meshQuadBuffer, int quadOffset, BufferWriter out, int expectedStride) { + val vertexSize = vertexSizeInQuadBuffer(); for(int vi = 0; vi < 4; vi++) { - out.writeFloat(meshQuad.xs[vi]); - out.writeFloat(meshQuad.ys[vi]); - out.writeFloat(meshQuad.zs[vi]); + int offset = quadOffset + vi * vertexSize; + out.writeFloat(Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_XPOS])); + out.writeFloat(Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_YPOS])); + out.writeFloat(Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_ZPOS])); - float u = meshQuad.us[vi]; - float v = meshQuad.vs[vi]; + float u = Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_U]); + float v = Float.intBitsToFloat(meshQuadBuffer[offset + QUAD_OFFSET_V]); if(Config.shortUV) { out.writeShort((short)(Math.round(u * 32768f))); @@ -74,9 +92,9 @@ public void writeMeshQuadToBuffer(MeshQuad meshQuad, BufferWriter out, int expec out.writeFloat(v); } - out.writeInt(meshQuad.cs[vi]); + out.writeInt(meshQuadBuffer[offset + QUAD_OFFSET_C]); - out.writeInt(meshQuad.bs[vi]); + out.writeInt(meshQuadBuffer[offset + QUAD_OFFSET_B]); assert out.position() % expectedStride == 0; } diff --git a/src/main/java/makamys/neodymium/util/GuiHelper.java b/src/main/java/makamys/neodymium/util/GuiHelper.java index 1b56d63..0ea414a 100644 --- a/src/main/java/makamys/neodymium/util/GuiHelper.java +++ b/src/main/java/makamys/neodymium/util/GuiHelper.java @@ -1,5 +1,7 @@ package makamys.neodymium.util; +import lombok.val; +import makamys.neodymium.Compat; import org.lwjgl.opengl.GL11; import net.minecraft.client.Minecraft; @@ -22,39 +24,36 @@ public static void begin() { GL11.glTranslatef(0.0F, 0.0F, -2000.0F); //GL11.glLineWidth(1.0F); //GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glEnable(GL11.GL_BLEND); + Tessellator tessellator = Compat.tessellator(); + tessellator.startDrawingQuads(); } public static void drawRectangle(int x, int y, int w, int h, int color) { - Tessellator tessellator = Tessellator.instance; - tessellator.startDrawingQuads(); + val tessellator = Compat.tessellator(); tessellator.setColorOpaque_I(color); tessellator.addVertex(x, y, 0); tessellator.addVertex(x, y+h, 0); tessellator.addVertex(x+w, y+h, 0); tessellator.addVertex(x+w, y, 0); - - tessellator.draw(); } + public static void drawRectangle(int x, int y, int w, int h, int color, int opacity) { - GL11.glEnable(GL11.GL_BLEND); - - Tessellator tessellator = Tessellator.instance; - tessellator.startDrawingQuads(); + val tessellator = Compat.tessellator(); tessellator.setColorRGBA_I(color, opacity); tessellator.addVertex(x, y, 0); tessellator.addVertex(x, y+h, 0); tessellator.addVertex(x+w, y+h, 0); tessellator.addVertex(x+w, y, 0); - - tessellator.draw(); - - GL11.glDisable(GL11.GL_BLEND); + } public static void end() { - //GL11.glDisable(GL11.GL_BLEND); - + val tessellator = Compat.tessellator(); + tessellator.draw(); + GL11.glDisable(GL11.GL_BLEND); + //GL11.glEnable(GL11.GL_TEXTURE_2D); } diff --git a/src/main/java/makamys/neodymium/util/OFUtil.java b/src/main/java/makamys/neodymium/util/OFUtil.java index 6c007ca..af968e5 100644 --- a/src/main/java/makamys/neodymium/util/OFUtil.java +++ b/src/main/java/makamys/neodymium/util/OFUtil.java @@ -46,6 +46,6 @@ private static void checkIfOptiFineIsPresent() { } private static boolean getIsFogOff() { - return ((IMixinGameSettings_OptiFine)Minecraft.getMinecraft().gameSettings).getOfFogType() == 3; + return ((IMixinGameSettings_OptiFine)Minecraft.getMinecraft().gameSettings).nd$getOfFogType() == 3; } } diff --git a/src/main/java/makamys/neodymium/util/Util.java b/src/main/java/makamys/neodymium/util/Util.java index 46cd0f0..51c59da 100644 --- a/src/main/java/makamys/neodymium/util/Util.java +++ b/src/main/java/makamys/neodymium/util/Util.java @@ -1,5 +1,14 @@ package makamys.neodymium.util; +import lombok.val; +import net.minecraft.client.Minecraft; +import net.minecraft.launchwrapper.Launch; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.net.URI; @@ -12,12 +21,6 @@ import java.nio.file.Files; import java.nio.file.Path; -import org.apache.commons.io.FileUtils; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; - -import net.minecraft.launchwrapper.Launch; - public class Util { private static boolean allowResourceOverrides = Boolean.parseBoolean(System.getProperty("neodymium.allowResourceOverrides", "false")); @@ -86,26 +89,36 @@ public static float[] floatBufferToArray(FloatBuffer buffer) { } public static double distSq(double x1, double y1, double z1, double x2, double y2, double z2) { - return Math.pow(x1 - x2, 2) + - Math.pow(y1 - y2, 2) + - Math.pow(z1 - z2, 2); + val dX = x1 - x2; + val dY = y1 - y2; + val dZ = z1 - z2; + return dX * dX + dY * dY + dZ * dZ; } - + public static void dumpTexture() { - int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH); - int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT); - - System.out.println("Dumped " + width + "x" + height + " texture."); - - ByteBuffer buf = BufferUtils.createByteBuffer(4 * width * height); - GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf); + val mc = Minecraft.getMinecraft(); + val workingPath = mc.mcDataDir.toPath(); + val terrainFile = workingPath.resolve("terrain.png").toFile(); + + val width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH); + val height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT); + + val buf = BufferUtils.createByteBuffer(4 * width * height); + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, buf); try { - // to convert to png: - // magick -size 512x256 -depth 8 out.rgba out.png - FileUtils.writeByteArrayToFile(new File("out.rgba"), Util.byteBufferToArray(buf)); + val img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + val intBuf = buf.asIntBuffer(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + img.setRGB(x, y, intBuf.get()); + } + } + ImageIO.write(img, "png", terrainFile); } catch (IOException e) { + ChatUtil.showNeoChatMessage("Failed to dump terrain texture", ChatUtil.MessageVerbosity.ERROR); e.printStackTrace(); } + ChatUtil.showChatMessage("Dumped terrain texture to: " + terrainFile.getAbsolutePath()); } public static int createBrightness(int sky, int block) {