From dcc8d8deed2bda01058cfbca4aa8a921a82d48cf Mon Sep 17 00:00:00 2001 From: Jason Mitchell Date: Thu, 28 May 2026 07:31:17 -0700 Subject: [PATCH] Make shader based clouds work with iris shaderpacks, and port optimizations from #1685 (#1793) * Make shader based clouds work with iris shaderpacks, and port optimizations from #1685 Co-Authored-By: Eclipse-Sol * Yeet GL_QUADS * Optimize a little more --------- Co-authored-by: Eclipse-Sol Co-authored-by: Logan Phillips --- .../angelica/glsm/GLStateManager.java | 33 +- .../angelica/glsm/hooks/GLSMHooks.java | 2 + .../hooks/events/ShaderColorChangeEvent.java | 10 + .../angelica/iris/IrisGLSMBridge.java | 12 +- .../angelica/render/CloudEdges.java | 75 ++ .../angelica/render/CloudRenderer.java | 1081 ++++++++++++----- .../angelica/render/CloudUniforms.java | 78 ++ .../iris/gl/state/StateUpdateNotifiers.java | 1 + .../transform/AttributeTransformer.java | 2 +- .../transform/CeleritasTransformer.java | 1 + .../pipeline/transform/CommonTransformer.java | 3 +- .../transform/DHGenericTransformer.java | 2 +- .../transform/DHTerrainTransformer.java | 2 +- .../iris/shaderpack/loading/ProgramId.java | 2 +- .../iris/uniforms/IrisExclusiveUniforms.java | 11 + .../iris/uniforms/IrisInternalUniforms.java | 11 + .../assets/angelica/shaders/cloud.frag | 17 +- .../assets/angelica/shaders/cloud.vert | 20 +- .../early/shaders/MixinEntityRenderer.java | 2 + 19 files changed, 1008 insertions(+), 357 deletions(-) create mode 100644 glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ShaderColorChangeEvent.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/render/CloudEdges.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/render/CloudUniforms.java diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java index 7d0123e0a..6ea5644a0 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java @@ -163,18 +163,39 @@ public class GLStateManager { fragmentGeneration++; } + @Getter private static float shaderColorR = 1.0f; + @Getter private static float shaderColorG = 1.0f; + @Getter private static float shaderColorB = 1.0f; + @Getter private static float shaderColorA = 1.0f; + + public static void setShaderColor(float r, float g, float b, float a) { + if (r == shaderColorR && g == shaderColorG && b == shaderColorB && a == shaderColorA) return; + shaderColorR = r; shaderColorG = g; shaderColorB = b; shaderColorA = a; + if (GLSMHooks.SHADER_COLOR_CHANGE.hasListeners()) { + GLSMHooks.shaderColorChangeEvent.red = r; + GLSMHooks.shaderColorChangeEvent.green = g; + GLSMHooks.shaderColorChangeEvent.blue = b; + GLSMHooks.shaderColorChangeEvent.alpha = a; + GLSMHooks.SHADER_COLOR_CHANGE.post(GLSMHooks.shaderColorChangeEvent); + } + } + // Deferred vertex attribute upload flags — set when state changes, flushed before draw - private static boolean dirtyColorAttrib; + private static boolean dirtyColorAttrib = true; private static boolean dirtyNormalAttrib; private static boolean dirtyTexCoordAttrib; - private static boolean dirtyLightmapAttrib; + private static boolean dirtyLightmapAttrib = true; /** Flush deferred vertex attribute uploads. Called before draw to ensure default attrib values are current. */ public static void flushDeferredVertexAttribs() { - RENDER_BACKEND.vertexAttrib4f(Usage.COLOR.getAttributeLocation(), color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); - dirtyColorAttrib = false; - RENDER_BACKEND.vertexAttrib4f(Usage.SECONDARY_UV.getAttributeLocation(), GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f); - dirtyLightmapAttrib = false; + if (dirtyColorAttrib) { + RENDER_BACKEND.vertexAttrib4f(Usage.COLOR.getAttributeLocation(), color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + dirtyColorAttrib = false; + } + if (dirtyLightmapAttrib) { + RENDER_BACKEND.vertexAttrib4f(Usage.SECONDARY_UV.getAttributeLocation(), GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f); + dirtyLightmapAttrib = false; + } if (dirtyNormalAttrib) { final var n = ShaderManager.getCurrentNormal(); RENDER_BACKEND.vertexAttrib3f(Usage.NORMAL.getAttributeLocation(), n.x, n.y, n.z); diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java index e5af220c3..fc1550af5 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java @@ -18,6 +18,7 @@ public final class GLSMHooks { public static final EventBus FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class); public static final EventBus LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class); public static final EventBus ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class); + public static final EventBus SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class); // Reusable event instances public static final TextureBindEvent textureBindEvent = new TextureBindEvent(); @@ -28,6 +29,7 @@ public final class GLSMHooks { public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent(); public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent(); public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent(); + public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent(); private GLSMHooks() {} } diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ShaderColorChangeEvent.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ShaderColorChangeEvent.java new file mode 100644 index 000000000..c19632f95 --- /dev/null +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ShaderColorChangeEvent.java @@ -0,0 +1,10 @@ +package com.gtnewhorizons.angelica.glsm.hooks.events; + +import net.minecraftforge.eventbus.api.event.MutableEvent; + +public final class ShaderColorChangeEvent extends MutableEvent { + public float red; + public float green; + public float blue; + public float alpha; +} diff --git a/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java b/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java index 33c246a94..cb149a240 100644 --- a/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java +++ b/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java @@ -26,6 +26,7 @@ public class IrisGLSMBridge { private static Runnable fogStartListener = null; private static Runnable fogEndListener = null; private static Runnable fogDensityListener = null; + private static Runnable colorModulatorListener = null; static { StateUpdateNotifiers.alphaFuncNotifier = listener -> alphaFuncListener = listener; @@ -35,6 +36,7 @@ public class IrisGLSMBridge { StateUpdateNotifiers.fogStartNotifier = listener -> fogStartListener = listener; StateUpdateNotifiers.fogEndNotifier = listener -> fogEndListener = listener; StateUpdateNotifiers.fogDensityNotifier = listener -> fogDensityListener = listener; + StateUpdateNotifiers.colorModulatorNotifier = listener -> colorModulatorListener = listener; } public static void register() { @@ -108,6 +110,10 @@ public class IrisGLSMBridge { } }); + GLSMHooks.SHADER_COLOR_CHANGE.addListener(event -> { + if (Iris.enabled && colorModulatorListener != null) colorModulatorListener.run(); + }); + GLSMHooks.FOG_STATE_CHANGE.addListener(event -> { if (Iris.enabled) { if (fogModeListener != null) fogModeListener.run(); @@ -157,17 +163,17 @@ public class IrisGLSMBridge { GLSMHooks.PROGRAM_CHANGE.addListener(event -> { if (!Iris.enabled) return; if (event.postBind) return; - + final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return; if (!drp.shouldOverrideShaders()) return; if (drp.getActivePassProgramId() == -1) return; - + if (DepthColorStorage.isOwnedProgram(event.newProgram)) { DepthColorStorage.unlockDepthColor(); } }); - + GLSMHooks.PROGRAM_CHANGE.addListener(event -> { if (!Iris.enabled) return; if (!event.postBind) return; diff --git a/src/main/java/com/gtnewhorizons/angelica/render/CloudEdges.java b/src/main/java/com/gtnewhorizons/angelica/render/CloudEdges.java new file mode 100644 index 000000000..868933f19 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/render/CloudEdges.java @@ -0,0 +1,75 @@ +package com.gtnewhorizons.angelica.render; + +import java.nio.ByteBuffer; + +final class CloudEdges { + private static final int CELL_EMPTY_THRESHOLD = 10; + + final int width, height; + private final int wMask, hMask; + private final boolean whPow2; + private final long[] opaqueBits; + final int opaqueCount; + + CloudEdges(int width, int height, ByteBuffer rgba) { + this.width = width; + this.height = height; + this.wMask = width - 1; + this.hMask = height - 1; + this.whPow2 = (width & wMask) == 0 && (height & hMask) == 0; + final int total = width * height; + final long[] bits = new long[(total + 63) >>> 6]; + int op = 0; + for (int i = 0; i < total; i++) { + if ((rgba.get(i * 4 + 3) & 0xFF) >= CELL_EMPTY_THRESHOLD) { + bits[i >>> 6] |= 1L << (i & 63); + op++; + } + } + this.opaqueBits = bits; + this.opaqueCount = op; + } + + boolean cellOpaque(int x, int z) { + final int xw, zh; + if (whPow2) { + xw = x & wMask; + zh = z & hMask; + } else { + xw = Math.floorMod(x, width); + zh = Math.floorMod(z, height); + } + final int idx = xw + zh * width; + return ((opaqueBits[idx >>> 6] >>> (idx & 63)) & 1L) != 0L; + } + + long readOpaqueRowBits(int x0, int z) { + final int zMod, x0Mod; + if (whPow2) { + zMod = z & hMask; + x0Mod = x0 & wMask; + } else { + zMod = Math.floorMod(z, height); + x0Mod = Math.floorMod(x0, width); + } + if (x0Mod + 10 <= width) { + return readContigBits(opaqueBits, (long) zMod * width + x0Mod, 10); + } + final int colsBeforeWrap = width - x0Mod; + final long firstPart = readContigBits(opaqueBits, (long) zMod * width + x0Mod, colsBeforeWrap); + final long secondPart = readContigBits(opaqueBits, (long) zMod * width, 10 - colsBeforeWrap); + return firstPart | (secondPart << colsBeforeWrap); + } + + private static long readContigBits(long[] bits, long startBitIdx, int count) { + final int wordIdx = (int) (startBitIdx >>> 6); + final int bitOff = (int) (startBitIdx & 63); + final long mask = count == 64 ? -1L : (1L << count) - 1L; + final long w0 = bits[wordIdx]; + if (bitOff + count <= 64) { + return (w0 >>> bitOff) & mask; + } + final long w1 = bits[wordIdx + 1]; + return ((w0 >>> bitOff) | (w1 << (64 - bitOff))) & mask; + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/render/CloudRenderer.java b/src/main/java/com/gtnewhorizons/angelica/render/CloudRenderer.java index 76cd80425..71ced4a19 100644 --- a/src/main/java/com/gtnewhorizons/angelica/render/CloudRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/render/CloudRenderer.java @@ -21,59 +21,118 @@ package com.gtnewhorizons.angelica.render; -import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator; -import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager; +import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities; import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject; import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType; +import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat; import com.gtnewhorizons.angelica.glsm.GLStateManager; -import com.gtnewhorizons.angelica.client.rendering.GlUniformFloat2v; import jss.notfine.core.Settings; import jss.notfine.gui.options.named.GraphicsQualityOff; +import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.EntityRenderer; -import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.entity.Entity; +import net.minecraft.util.MathHelper; import net.minecraft.util.ResourceLocation; import net.minecraft.util.Vec3; import org.embeddedt.embeddium.impl.gl.shader.GlProgram; import org.embeddedt.embeddium.impl.gl.shader.GlShader; -import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext; import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants; import org.embeddedt.embeddium.impl.gl.shader.ShaderType; -import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat3v; -import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v; -import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformInt; -import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f; import org.embeddedt.embeddium.impl.render.shader.ShaderLoader; import org.joml.Matrix4f; import org.joml.Matrix4fStack; +import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL15; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; public class CloudRenderer implements IResourceManagerReloadListener { - private static final float PX_SIZE = 1 / 256F; - private static final int TOP_SECTIONS = 12; - private static final int HEIGHT = 4; - private static final float INSET = 0.001F; - private static final float ALPHA = 0.8F; + private static final byte F_TOP = 0, F_BOTTOM = 1, F_X = 2, F_Z = 3; + private static final float[] FACTOR_VALUES = {0.7f, 1.0f, 0.9f, 0.8f}; // {top, bottom, X, Z} + private static final int[] FACTOR_BYTE_SHIFTED; + static { + FACTOR_BYTE_SHIFTED = new int[4]; + for (int i = 0; i < 4; i++) { + final int b = Math.round(FACTOR_VALUES[i] * 255f) & 0xFF; + FACTOR_BYTE_SHIFTED[i] = (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) ? b : (b << 24); + } + } + private static final int FACTOR_ATTRIB_LOC = 5; - private static final boolean WIREFRAME = false; + private static final int VERTEX_STRIDE = 24; + private static final int NORMAL_OFFSET = 20; + private static final int QUAD_BYTES = 4 * VERTEX_STRIDE; + + private static final int CELLS_PER_CHUNK = 8; + + private static final float SCROLL_SPEED = 1.0f / 256.0f; + private static final float EDGE_OVERLAP = 0.0001f; + + private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + + private static final int N_POS_X = packNormalInt((byte) 127, (byte) 0, (byte) 0); + private static final int N_NEG_X = packNormalInt((byte) -127, (byte) 0, (byte) 0); + private static final int N_POS_Y = packNormalInt((byte) 0, (byte) 127, (byte) 0); + private static final int N_NEG_Y = packNormalInt((byte) 0, (byte) -127, (byte) 0); + private static final int N_POS_Z = packNormalInt((byte) 0, (byte) 0, (byte) 127); + private static final int N_NEG_Z = packNormalInt((byte) 0, (byte) 0, (byte) -127); private final Minecraft mc = Minecraft.getMinecraft(); private final ResourceLocation texture = new ResourceLocation("textures/environment/clouds.png"); - private IVertexArrayObject vao; - private int cloudMode = -1; - private int renderDistance = -1; - private int cloudElevation = -1; - private int scaleMult = -1; + private CloudEdges edges; + private int edgesGeneration; + private int texW, texH; + private int mipmapTexId = -1; + private int cloudTexId = -1; - private int texW; - private int texH; + private IVertexArrayObject vao; + private int cachedVertexCount; + private int eboId = -1; + private int eboQuadCapacity; + + private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE; + private int anchorMarginCells = 0; + private int cachedEdgesGeneration = Integer.MIN_VALUE; + private boolean cachedEmitTopFace, cachedEmitBottomFace, cachedCameraInsideCloud; + private int cachedRenderRadius = -1; + private float cachedCloudInteriorHeight = Float.NaN; + + private int cloudMode = -1, renderDistance = -1, cloudElevation = -1, scaleMult = -1; + + private ByteBuffer emitBuffer; + private long emitBufferAddr; + private int emitBufferCapacity; + private long writeAddr, writeEnd; + + private ByteBuffer sortedBuffer; + private long sortedBufferAddr; + private int sortedBufferCapacity; + + private ByteBuffer edgeStagingBuffer; + private int edgeStagingCapacity; + + private byte[] quadFactor = new byte[2048]; + private int quadCount; + + private final int[] factorQuadCount = new int[4]; + private final int[] factorVertOffset = new int[4]; + private final long[] factorWriteAddr = new long[4]; + + private final int[] chunkOpaqueRows = new int[10]; + private long[] globalEmit; + private long[] globalEmitTodo; + private int gridRows, gridCols, gridLongsPerRow, gridMinX, gridMinZ; + private long[] runMaskScratch = new long[8]; private GlProgram program; private final Matrix4f mvpScratch = new Matrix4f(); @@ -83,207 +142,57 @@ public class CloudRenderer implements IResourceManagerReloadListener { } private static CloudRenderer instance; + public static CloudRenderer getCloudRenderer() { - if (instance == null) - instance = new CloudRenderer(); + if (instance == null) instance = new CloudRenderer(); return instance; } - private int getScale() { - return (cloudMode == 2 ? 12 : 8) * scaleMult; + public static int getCloudTextureWidth() { + return instance != null && instance.texW > 0 ? instance.texW : 256; } - private float ceilToScale(float value) { - final float scale = getScale(); - return (float)Math.ceil(value / scale) * scale; - } - - private float meshExtent() { - return ceilToScale(renderDistance * 4 * 16); - } - - private void vertices(Tessellator tessellator) { - final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff)Settings.MODE_CLOUDS.option.getStore(); - final boolean fancy = cloudGraphicsQuality == GraphicsQualityOff.FANCY || cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics; - - final float scale = getScale(); - final float CULL_DIST = 2 * scale; - - final float bCol = fancy ? 0.7F : 1F; - - final float sectEnd = meshExtent(); - final float sectStart = -sectEnd; - - final float sectStep = ceilToScale(sectEnd * 2 / TOP_SECTIONS); - final float sectPx = PX_SIZE / scale; - - tessellator.startDrawingQuads(); - - float sectX0 = sectStart; - float sectX1 = sectX0; - - while (sectX1 < sectEnd) { - sectX1 += sectStep; - - if (sectX1 > sectEnd) - sectX1 = sectEnd; - - float sectZ0 = sectStart; - float sectZ1 = sectZ0; - - while (sectZ1 < sectEnd) { - sectZ1 += sectStep; - - if (sectZ1 > sectEnd) - sectZ1 = sectEnd; - - final float u0 = sectX0 * sectPx; - final float u1 = sectX1 * sectPx; - final float v0 = sectZ0 * sectPx; - final float v1 = sectZ1 * sectPx; - - // Bottom - tessellator.setColorRGBA_F(bCol, bCol, bCol, ALPHA); - tessellator.addVertexWithUV(sectX0, 0, sectZ0, u0, v0); - tessellator.addVertexWithUV(sectX1, 0, sectZ0, u1, v0); - tessellator.addVertexWithUV(sectX1, 0, sectZ1, u1, v1); - tessellator.addVertexWithUV(sectX0, 0, sectZ1, u0, v1); - - if (fancy) { - final int height = HEIGHT * scaleMult; - // Top - tessellator.setColorRGBA_F(1, 1, 1, ALPHA); - tessellator.addVertexWithUV(sectX0, height, sectZ0, u0, v0); - tessellator.addVertexWithUV(sectX0, height, sectZ1, u0, v1); - tessellator.addVertexWithUV(sectX1, height, sectZ1, u1, v1); - tessellator.addVertexWithUV(sectX1, height, sectZ0, u1, v0); - - float slice; - float sliceCoord0; - float sliceCoord1; - - for (slice = sectX0; slice < sectX1; ) { - sliceCoord0 = slice * sectPx; - sliceCoord1 = sliceCoord0 + PX_SIZE; - - // X sides - if (slice > -CULL_DIST) { - slice += INSET; - tessellator.setColorRGBA_F(0.9F, 0.9F, 0.9F, ALPHA); - tessellator.addVertexWithUV(slice, 0, sectZ1, sliceCoord0, v1); - tessellator.addVertexWithUV(slice, height, sectZ1, sliceCoord1, v1); - tessellator.addVertexWithUV(slice, height, sectZ0, sliceCoord1, v0); - tessellator.addVertexWithUV(slice, 0, sectZ0, sliceCoord0, v0); - slice -= INSET; - } - - slice += scale; - - if (slice <= CULL_DIST) { - slice -= INSET; - tessellator.setColorRGBA_F(0.9F, 0.9F, 0.9F, ALPHA); - tessellator.addVertexWithUV(slice, 0, sectZ0, sliceCoord0, v0); - tessellator.addVertexWithUV(slice, height, sectZ0, sliceCoord1, v0); - tessellator.addVertexWithUV(slice, height, sectZ1, sliceCoord1, v1); - tessellator.addVertexWithUV(slice, 0, sectZ1, sliceCoord0, v1); - slice += INSET; - } - } - - for (slice = sectZ0; slice < sectZ1; ) { - sliceCoord0 = slice * sectPx; - sliceCoord1 = sliceCoord0 + PX_SIZE; - - // Z sides - if (slice > -CULL_DIST) { - slice += INSET; - tessellator.setColorRGBA_F(0.8F, 0.8F, 0.8F, ALPHA); - tessellator.addVertexWithUV(sectX0, 0, slice, u0, sliceCoord0); - tessellator.addVertexWithUV(sectX0, height, slice, u0, sliceCoord1); - tessellator.addVertexWithUV(sectX1, height, slice, u1, sliceCoord1); - tessellator.addVertexWithUV(sectX1, 0, slice, u1, sliceCoord0); - slice -= INSET; - } - - slice += scale; - - if (slice <= CULL_DIST) { - slice -= INSET; - tessellator.setColorRGBA_F(0.8F, 0.8F, 0.8F, ALPHA); - tessellator.addVertexWithUV(sectX1, 0, slice, u1, sliceCoord0); - tessellator.addVertexWithUV(sectX1, height, slice, u1, sliceCoord1); - tessellator.addVertexWithUV(sectX0, height, slice, u0, sliceCoord1); - tessellator.addVertexWithUV(sectX0, 0, slice, u0, sliceCoord0); - slice += INSET; - } - } - } - - sectZ0 = sectZ1; - } - - sectX0 = sectX1; - } - } - - private void dispose() { - if (vao != null) { - vao.delete(); - vao = null; - } - if (program != null) { - program.delete(); - program = null; - } - } - - private void build() { - final DirectTessellator tessellator = TessellatorManager.startCapturingDirect(); - this.vertices(tessellator); - this.vao = DirectTessellator.stopCapturingToVBO(VertexBufferType.IMMUTABLE); - } - - private int fullCoord(double coord, int scale) { // Corrects misalignment of UV offset when on negative coords. - return ((int) coord / scale) - (coord < 0 ? 1 : 0); - } - - private boolean isBuilt() { - return vao != null; + public static int getScaleMult() { + return instance != null && instance.scaleMult > 0 ? instance.scaleMult : 1; } public void checkSettings() { - final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff)Settings.MODE_CLOUDS.option.getStore(); + final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff) Settings.MODE_CLOUDS.option.getStore(); final int cloudQualitySetting = cloudGraphicsQuality == GraphicsQualityOff.FANCY || cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics ? 2 : 1; final boolean newEnabled = cloudGraphicsQuality != GraphicsQualityOff.OFF && mc.gameSettings.shouldRenderClouds() && mc.theWorld != null && mc.theWorld.provider.isSurfaceWorld(); - final int targetDistance = Math.max(mc.gameSettings.renderDistanceChunks, (int)Settings.RENDER_DISTANCE_CLOUDS.option.getStore()); - final int cloudScaleMult = (int)Settings.CLOUD_SCALE.option.getStore(); + final int targetDistance = Math.max(mc.gameSettings.renderDistanceChunks, (int) Settings.RENDER_DISTANCE_CLOUDS.option.getStore()); + final int cloudScaleMult = (int) Settings.CLOUD_SCALE.option.getStore(); - if (isBuilt() - && (!newEnabled + if (!newEnabled || cloudQualitySetting != cloudMode || targetDistance != renderDistance - || cloudScaleMult != scaleMult)) { - dispose(); + || cloudScaleMult != scaleMult) { + invalidateGeometry(); } cloudMode = cloudQualitySetting; renderDistance = targetDistance; scaleMult = cloudScaleMult; - cloudElevation = mc.theWorld == null ? 128 : (int)mc.theWorld.provider.getCloudHeight(); - //Allows the setting to work with RFG and similar without hardcoding. - //The minimum height check is so stuff like Aether cloud height doesn't get messed up. - if(cloudElevation >= 96) { - cloudElevation = (int)Settings.CLOUD_HEIGHT.option.getStore(); + cloudElevation = mc.theWorld == null ? 128 : (int) mc.theWorld.provider.getCloudHeight(); + // Allows the setting to work with RFG and similar without hardcoding. + // The minimum height check is so stuff like Aether cloud height doesn't get messed up. + if (cloudElevation >= 96) { + cloudElevation = (int) Settings.CLOUD_HEIGHT.option.getStore(); } + } - if (newEnabled && !isBuilt()) { - build(); - } + private void invalidateGeometry() { + anchorCellX = Integer.MIN_VALUE; + anchorCellZ = Integer.MIN_VALUE; + cachedRenderRadius = -1; + cachedEdgesGeneration = Integer.MIN_VALUE; + cachedCloudInteriorHeight = Float.NaN; + cachedVertexCount = 0; } private void initProgram() { @@ -301,196 +210,724 @@ public class CloudRenderer implements IResourceManagerReloadListener { frag.delete(); } - final CloudUniforms u = program.getInterface(); - final float start = renderDistance * 16.0f; - final float end = meshExtent(); - final float range = end - start; - program.bind(); - u.cloudTex.setInt(0); - u.fogParams.set(new float[] { - range != 0.0f ? -1.0f / range : 0.0f, - range != 0.0f ? end / range : 1.0f, - 0.0f, 0.0f - }); + program.getInterface().tex.setInt(0); program.unbind(); } - private void uploadFogUniforms(float partialTicks) { + private void uploadFogUniforms(float partialTicks, int renderRadius, float cloudInteriorWidth) { final CloudUniforms u = program.getInterface(); final boolean fogEnabled = GLStateManager.getFogMode().isEnabled(); u.setFogEnabled(fogEnabled); - if (fogEnabled) { + final float start = renderDistance * 16.0f; + final float end = renderRadius * CELLS_PER_CHUNK * cloudInteriorWidth; + final float range = end - start; + u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? end / range : 1.0f); final Vec3 sky = mc.theWorld.getSkyColor(mc.renderViewEntity, partialTicks); u.setFogColor((float) sky.xCoord, (float) sky.yCoord, (float) sky.zCoord); } } - private static class CloudUniforms { - final GlUniformMatrix4f mvp; - final GlUniformFloat3v colorMult; - final GlUniformFloat2v texOffset; - final GlUniformFloat2v fogOrigin; - final GlUniformFloat4v fogParams; - final GlUniformFloat4v fogColor; - final GlUniformInt fogEnabled; - final GlUniformInt cloudTex; - - private int lastFogEnabled = -1; - private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN; - private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN; - private final float[] fogColorBuf = new float[4]; - - CloudUniforms(ShaderBindingContext ctx) { - mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new); - colorMult = ctx.bindUniform("u_ColorMultiplier", GlUniformFloat3v::new); - texOffset = ctx.bindUniform("u_TexOffset", GlUniformFloat2v::new); - fogOrigin = ctx.bindUniform("u_FogOrigin", GlUniformFloat2v::new); - fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new); - fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new); - fogEnabled = ctx.bindUniform("u_FogEnabled", GlUniformInt::new); - cloudTex = ctx.bindUniform("u_CloudTexture", GlUniformInt::new); - } - - void setFogEnabled(boolean enabled) { - final int val = enabled ? 1 : 0; - if (val != lastFogEnabled) { - lastFogEnabled = val; - fogEnabled.setInt(val); - } - } - - void setFogColor(float r, float g, float b) { - if (r != lastFogR || g != lastFogG || b != lastFogB) { - lastFogR = r; lastFogG = g; lastFogB = b; - fogColorBuf[0] = r; fogColorBuf[1] = g; fogColorBuf[2] = b; fogColorBuf[3] = 1.0f; - fogColor.set(fogColorBuf); - } - } - - void setColorMult(float r, float g, float b) { - if (r != lastColorR || g != lastColorG || b != lastColorB) { - lastColorR = r; lastColorG = g; lastColorB = b; - colorMult.set(r, g, b); - } - } - } - public boolean render(int cloudTicks, float partialTicks) { - if (!isBuilt()) - return false; + if (mc.theWorld == null || mc.renderViewEntity == null) return false; + if (cloudMode != 2 || scaleMult <= 0) return false; + boolean textureSetupDone = false; + if (edges == null) { + if (mc.renderEngine == null) return false; + mc.renderEngine.bindTexture(texture); + setupCloudTexture(); + textureSetupDone = true; + if (edges == null) return false; + } if (program == null) { initProgram(); if (program == null) return false; } final Entity entity = mc.renderViewEntity; + final float cloudInteriorWidth = 12.0f * scaleMult; + final float cloudInteriorHeight = 4.0f * scaleMult; + final double cloudTick = cloudTicks + (double) partialTicks; - final double totalOffset = cloudTicks + partialTicks; + final float cameraOffsetY = (float) (entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks); + double cameraOffsetX = (entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks + cloudTick * 0.03D) / cloudInteriorWidth; + double cameraOffsetZ = (entity.prevPosZ + (entity.posZ - entity.prevPosZ) * partialTicks) / cloudInteriorWidth; + cameraOffsetX -= MathHelper.floor_double(cameraOffsetX / 2048.0D) * 2048; + cameraOffsetZ -= MathHelper.floor_double(cameraOffsetZ / 2048.0D) * 2048; - final double x = entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks + totalOffset * 0.03; - final double y = cloudElevation - (entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks) + 0.33; - double z = entity.prevPosZ + (entity.posZ - entity.prevPosZ) * partialTicks; + final float cameraRelativeY = cloudElevation - cameraOffsetY + 0.33F; + final float cameraRelativeX = (float) (cameraOffsetX - MathHelper.floor_double(cameraOffsetX)); + final float cameraRelativeZ = (float) (cameraOffsetZ - MathHelper.floor_double(cameraOffsetZ)); - final int scale = getScale(); + final int anchorX = MathHelper.floor_double(cameraOffsetX); + final int anchorZ = MathHelper.floor_double(cameraOffsetZ); - if (cloudMode == 2) - z += 0.33 * scale; + final boolean emitTopFace = cameraRelativeY + cloudInteriorHeight >= 0.0F; + final boolean emitBottomFace = cameraRelativeY <= 0.0F; + final boolean cameraInsideY = cameraRelativeY <= 0.0F && cameraRelativeY + cloudInteriorHeight >= 0.0F; + final boolean cameraInsideCloud = cameraInsideY && edges.cellOpaque(anchorX, anchorZ); - int offU = fullCoord(x, scale); - int offV = fullCoord(z, scale); + final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth)); - GLStateManager.glPushMatrix(); - GLStateManager.glTranslated((offU * scale) - x, y, (offV * scale) - z); + final boolean anchorInRange = vao != null + && Math.abs(anchorX - anchorCellX) <= anchorMarginCells + && Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells; + final boolean geomCacheValid = anchorInRange + && cachedEdgesGeneration == edgesGeneration + && cachedEmitTopFace == emitTopFace + && cachedEmitBottomFace == emitBottomFace + && cachedCameraInsideCloud == cameraInsideCloud + && cachedRenderRadius == renderRadius + && cachedCloudInteriorHeight == cloudInteriorHeight; - final float fogOriginX = (float)(x - offU * scale); - final float fogOriginZ = (float)(z - offV * scale); + if (!geomCacheValid) { + final int marginCells = cameraInsideCloud ? 0 : Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4); + rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, cameraInsideCloud, marginCells); + anchorCellX = anchorX; + anchorCellZ = anchorZ; + anchorMarginCells = marginCells; + cachedEdgesGeneration = edgesGeneration; + cachedEmitTopFace = emitTopFace; + cachedEmitBottomFace = emitBottomFace; + cachedCameraInsideCloud = cameraInsideCloud; + cachedRenderRadius = renderRadius; + cachedCloudInteriorHeight = cloudInteriorHeight; + } - offU = offU % texW; - offV = offV % texH; + if (vao == null || cachedVertexCount == 0) return true; - GLStateManager.disableCull(); - - GLStateManager.enableBlend(); - GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GLStateManager.glActiveTexture(GL13.GL_TEXTURE0); + mc.renderEngine.bindTexture(texture); + if (!textureSetupDone) setupCloudTexture(); final Vec3 color = mc.theWorld.getCloudColour(partialTicks); float r = (float) color.xCoord; float g = (float) color.yCoord; float b = (float) color.zCoord; - if (mc.gameSettings.anaglyph) { - final float tempR = r * 0.3F + g * 0.59F + b * 0.11F; - final float tempG = r * 0.3F + g * 0.7F; - final float tempB = r * 0.3F + b * 0.7F; + final float tempR = (r * 30.0F + g * 59.0F + b * 11.0F) / 100.0F; + final float tempG = (r * 30.0F + g * 70.0F) / 100.0F; + final float tempB = (r * 30.0F + b * 70.0F) / 100.0F; r = tempR; g = tempG; b = tempB; } - GLStateManager.glActiveTexture(GL13.GL_TEXTURE0); - mc.renderEngine.bindTexture(texture); - program.bind(); + final boolean shadersActive = IrisApi.getInstance().isShaderPackInUse(); - final CloudUniforms u = program.getInterface(); - final Matrix4fStack mv = GLStateManager.getModelViewMatrix(); - GLStateManager.getProjectionMatrix().mul(mv, mvpScratch); - u.mvp.set(mvpScratch); - u.texOffset.set(offU * PX_SIZE, offV * PX_SIZE); - u.setColorMult(r, g, b); - u.fogOrigin.set(fogOriginX, fogOriginZ); + final float origFogStart = GLStateManager.getFogState().getStart(); + final float origFogEnd = GLStateManager.getFogState().getEnd(); + final float cloudExtentBlocks = renderRadius * CELLS_PER_CHUNK * cloudInteriorWidth; + final boolean pushFog = GLStateManager.getFogMode().isEnabled() && GLStateManager.getFogState().getFogMode() == GL11.GL_LINEAR && origFogEnd < cloudExtentBlocks; + if (pushFog) { + final float ratio = cloudExtentBlocks / origFogEnd; + GLStateManager.glFogf(GL11.GL_FOG_START, origFogStart * ratio); + GLStateManager.glFogf(GL11.GL_FOG_END, cloudExtentBlocks); + } - uploadFogUniforms(partialTicks); + final float anchorDriftX = (float) (anchorX - anchorCellX); + final float anchorDriftZ = (float) (anchorZ - anchorCellZ); + if (!shadersActive) { + program.bind(); + final CloudUniforms u = program.getInterface(); + final Matrix4fStack mv = GLStateManager.getModelViewMatrix(); + mv.pushMatrix(); + mv.scale(cloudInteriorWidth, 1.0f, cloudInteriorWidth); + mv.translate(-(cameraRelativeX + anchorDriftX), cameraRelativeY, -(cameraRelativeZ + anchorDriftZ)); + GLStateManager.getProjectionMatrix().mul(mv, mvpScratch); + mv.popMatrix(); + u.mvp.set(mvpScratch); + uploadFogUniforms(partialTicks, renderRadius, cloudInteriorWidth); + + drawAllFactors(r, g, b, false); + program.unbind(); + } else { + GLStateManager.glPushMatrix(); + GLStateManager.glScalef(cloudInteriorWidth, 1.0f, cloudInteriorWidth); + GLStateManager.glTranslatef(-(cameraRelativeX + anchorDriftX), cameraRelativeY, -(cameraRelativeZ + anchorDriftZ)); + drawAllFactors(r, g, b, true); + GLStateManager.glPopMatrix(); + } + + if (pushFog) { + GLStateManager.glFogf(GL11.GL_FOG_START, origFogStart); + GLStateManager.glFogf(GL11.GL_FOG_END, origFogEnd); + } + return true; + } + + private void drawAllFactors(float r, float g, float b, boolean shadersActive) { vao.bind(); + GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId); - // Depth pass to prevent insides rendering from the outside. + GLStateManager.disableBlend(); + GLStateManager.glDepthMask(true); GLStateManager.glColorMask(false, false, false, false); - vao.draw(); + // Depth prepass: alpha must match color pass so discard threshold writes same depth coverage. + if (shadersActive) { + GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, CloudUniforms.ALPHA); + } else { + program.getInterface().setColorMult(1.0f, 1.0f, 1.0f); + } + final int totalQuads = cachedVertexCount / 4; + GLStateManager.glDrawElements(GL11.GL_TRIANGLES, totalQuads * 6, GL11.GL_UNSIGNED_INT, 0L); if (!mc.gameSettings.anaglyph) { GLStateManager.glColorMask(true, true, true, true); } else { switch (EntityRenderer.anaglyphField) { - case 0: - GLStateManager.glColorMask(false, true, true, true); - break; - case 1: - GLStateManager.glColorMask(true, false, false, true); - break; + case 0 -> GLStateManager.glColorMask(false, true, true, true); + case 1 -> GLStateManager.glColorMask(true, false, false, true); + default -> GLStateManager.glColorMask(true, true, true, true); + } + } + GLStateManager.enableBlend(); + GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GLStateManager.glDepthMask(false); + + if (shadersActive) { + for (int f = 0; f < 4; f++) { + final int qc = factorQuadCount[f]; + if (qc == 0) continue; + final float v = FACTOR_VALUES[f]; + GLStateManager.glColor4f(v * r, v * g, v * b, CloudUniforms.ALPHA); + final long byteOffset = (long) (factorVertOffset[f] / 4) * 6L * 4L; + GLStateManager.glDrawElements(GL11.GL_TRIANGLES, qc * 6, GL11.GL_UNSIGNED_INT, byteOffset); + } + } else { + program.getInterface().setColorMult(r, g, b); + GLStateManager.glDrawElements(GL11.GL_TRIANGLES, totalQuads * 6, GL11.GL_UNSIGNED_INT, 0L); + } + + vao.unbind(); + GLStateManager.glDepthMask(true); + GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + GLStateManager.disableBlend(); + } + + private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, boolean cameraInsideCloud, int marginCells) { + prepareEmitBuffer(); + + final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK; + final int chunkRange = cameraInsideCloud ? 0 : (renderRadius + marginChunks); + final int outerLo = cameraInsideCloud ? -1 : -chunkRange; + final int outerHi = cameraInsideCloud ? 0 : chunkRange; + + final int radiusCellsWithMargin = cameraInsideCloud ? CELLS_PER_CHUNK : (chunkRange * CELLS_PER_CHUNK); + final int radiusCellsSq = radiusCellsWithMargin * radiusCellsWithMargin; + + final float scrollX = anchorX * SCROLL_SPEED; + final float scrollZ = anchorZ * SCROLL_SPEED; + + final int rows = (outerHi - outerLo + 1) * CELLS_PER_CHUNK; + final int lpr = (rows + 63) >>> 6; + final int minX = outerLo * CELLS_PER_CHUNK; + final int minZ = outerLo * CELLS_PER_CHUNK; + final int gridSize = rows * lpr; + if (emitTopFace || emitBottomFace) { + if (globalEmit == null || globalEmit.length < gridSize) { + globalEmit = new long[gridSize]; + globalEmitTodo = new long[gridSize]; + } else { + java.util.Arrays.fill(globalEmit, 0, gridSize, 0L); + } + this.gridRows = rows; + this.gridCols = rows; + this.gridLongsPerRow = lpr; + this.gridMinX = minX; + this.gridMinZ = minZ; + } + + final double y1 = cloudInteriorHeight; + final double ye = y1 - EDGE_OVERLAP; + + for (int chunkX = outerLo; chunkX <= outerHi; chunkX++) { + for (int chunkZ = outerLo; chunkZ <= outerHi; chunkZ++) { + emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, cameraInsideCloud, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ); } } - if (WIREFRAME) { - GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE); - GLStateManager.glLineWidth(2.0F); - GLStateManager.glDepthMask(false); - vao.draw(); - GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); - GLStateManager.glDepthMask(true); + if (emitTopFace || emitBottomFace) { + emitTopBottomGlobalGreedy(emitTopFace, emitBottomFace, ye, scrollX, scrollZ); } - vao.draw(); - vao.unbind(); // Unbind buffer. + final int totalBytes = (int) (writeAddr - emitBufferAddr); + final int vertexCount = totalBytes / VERTEX_STRIDE; + if (vertexCount == 0) { + if (vao != null) { + vao.delete(); + vao = null; + } + cachedVertexCount = 0; + return; + } - program.unbind(); + sortQuadsByFactor(); - GLStateManager.disableBlend(); - GLStateManager.enableCull(); + if (vao == null) { + vao = VertexBufferType.MUTABLE_RESIZABLE.allocate( + DefaultVertexFormat.POSITION_TEXTURE_NORMAL, GL11.GL_TRIANGLES, + sortedBuffer, vertexCount); + setupFactorAttrib(); + } else { + vao.getVBO().allocate(sortedBuffer, vertexCount); + } + cachedVertexCount = vertexCount; + ensureEbo(vertexCount / 4); + } - GLStateManager.glPopMatrix(); + private void setupFactorAttrib() { + vao.bind(); + GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vao.getVBO().getId()); + GLStateManager.glEnableVertexAttribArray(FACTOR_ATTRIB_LOC); + GLStateManager.glVertexAttribPointer(FACTOR_ATTRIB_LOC, 1, GL11.GL_UNSIGNED_BYTE, true, VERTEX_STRIDE, NORMAL_OFFSET + 3); + GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + vao.unbind(); + } - return true; + private void ensureEbo(int quadCount) { + if (eboId == -1) { + eboId = GLStateManager.glGenBuffers(); + } + if (quadCount <= eboQuadCapacity) return; + final java.nio.IntBuffer ib = BufferUtils.createIntBuffer(quadCount * 6); + for (int q = 0; q < quadCount; q++) { + final int v = q * 4; + ib.put(v).put(v + 1).put(v + 2); + ib.put(v).put(v + 2).put(v + 3); + } + ib.flip(); + GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId); + GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ib, GL15.GL_STATIC_DRAW); + GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); + eboQuadCapacity = quadCount; + } + + private void deleteEbo() { + if (eboId != -1) { + GLStateManager.glDeleteBuffers(eboId); + eboId = -1; + eboQuadCapacity = 0; + } + } + + private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean cameraInsideCloud, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) { + final float chunkOffsetX = chunkX * (float) CELLS_PER_CHUNK; + final float chunkOffsetZ = chunkZ * (float) CELLS_PER_CHUNK; + final int baseU = chunkX * CELLS_PER_CHUNK + anchorX; + final int baseV = chunkZ * CELLS_PER_CHUNK + anchorZ; + + final int[] opaqueRows = this.chunkOpaqueRows; + for (int row = 0; row < 10; row++) { + opaqueRows[row] = (int) edges.readOpaqueRowBits(baseU - 1, baseV + row - 1); + } + + long emittableBits = 0L; + for (int j = 0; j < CELLS_PER_CHUNK; j++) { + int rowOpaque = (opaqueRows[j + 1] >>> 1) & 0xFF; + while (rowOpaque != 0) { + final int k = Integer.numberOfTrailingZeros(rowOpaque); + rowOpaque &= rowOpaque - 1; + final int relX = chunkX * CELLS_PER_CHUNK + k; + final int relZ = chunkZ * CELLS_PER_CHUNK + j; + if (relX * relX + relZ * relZ > radiusCellsSq) continue; + if (cameraInsideCloud && (Math.abs(relX) > 1 || Math.abs(relZ) > 1)) continue; + emittableBits |= 1L << (j * CELLS_PER_CHUNK + k); + } + } + if (emittableBits == 0L) return; + + if (cameraInsideCloud) { + long bits = emittableBits; + while (bits != 0L) { + final int bit = Long.numberOfTrailingZeros(bits); + bits &= bits - 1; + final int k = bit & 7; + final int j = bit >>> 3; + final double x0 = chunkOffsetX + k; + final double x1 = chunkOffsetX + k + 1; + final double z0 = chunkOffsetZ + j; + final double z1 = chunkOffsetZ + j + 1; + final double xe = x1 - EDGE_OVERLAP; + final double zs = z1 - EDGE_OVERLAP; + final float uC = (chunkOffsetX + k + 0.5F) * SCROLL_SPEED + scrollX; + final float vC = (chunkOffsetZ + j + 0.5F) * SCROLL_SPEED + scrollZ; + final float uL = (chunkOffsetX + k) * SCROLL_SPEED + scrollX; + final float uR = (chunkOffsetX + k + 1) * SCROLL_SPEED + scrollX; + final float vN = (chunkOffsetZ + j) * SCROLL_SPEED + scrollZ; + final float vS = (chunkOffsetZ + j + 1) * SCROLL_SPEED + scrollZ; + + ensureEmitCapacity(6 * QUAD_BYTES); + emitCloudQuad(x0, 0.0, z1, uL, vS, x1, 0.0, z1, uR, vS, x1, 0.0, z0, uR, vN, x0, 0.0, z0, uL, vN, F_TOP, N_POS_Y); + emitCloudQuad(x0, ye, z0, uL, vN, x1, ye, z0, uR, vN, x1, ye, z1, uR, vS, x0, ye, z1, uL, vS, F_BOTTOM, N_NEG_Y); + emitCloudQuad(x0, 0.0, z0, uC, vN, x0, y1, z0, uC, vN, x0, y1, z1, uC, vS, x0, 0.0, z1, uC, vS, F_X, N_POS_X); + emitCloudQuad(xe, 0.0, z1, uC, vS, xe, y1, z1, uC, vS, xe, y1, z0, uC, vN, xe, 0.0, z0, uC, vN, F_X, N_NEG_X); + emitCloudQuad(x0, 0.0, z0, uL, vC, x1, 0.0, z0, uR, vC, x1, y1, z0, uR, vC, x0, y1, z0, uL, vC, F_Z, N_POS_Z); + emitCloudQuad(x0, y1, zs, uL, vC, x1, y1, zs, uR, vC, x1, 0.0, zs, uR, vC, x0, 0.0, zs, uL, vC, F_Z, N_NEG_Z); + } + return; + } + + if (emitTopFace || emitBottomFace) { + final int globalColStart = chunkX * CELLS_PER_CHUNK - gridMinX; + final int globalRowStart = chunkZ * CELLS_PER_CHUNK - gridMinZ; + final int wordIdx = globalColStart >>> 6; + final int bitOff = globalColStart & 63; + final long[] gE = this.globalEmit; + final int lpr = this.gridLongsPerRow; + if (bitOff <= 56) { + for (int j = 0; j < CELLS_PER_CHUNK; j++) { + final long row8 = (emittableBits >>> (j * CELLS_PER_CHUNK)) & 0xFFL; + if (row8 == 0L) continue; + gE[(globalRowStart + j) * lpr + wordIdx] |= row8 << bitOff; + } + } else { + final int hiShift = 64 - bitOff; + for (int j = 0; j < CELLS_PER_CHUNK; j++) { + final long row8 = (emittableBits >>> (j * CELLS_PER_CHUNK)) & 0xFFL; + if (row8 == 0L) continue; + final int gRowOff = (globalRowStart + j) * lpr; + gE[gRowOff + wordIdx] |= row8 << bitOff; + gE[gRowOff + wordIdx + 1] |= row8 >>> hiShift; + } + } + } + + int rowsAnd = 0x3FF; + for (int r = 0; r < 10 && rowsAnd == 0x3FF; r++) rowsAnd &= opaqueRows[r]; + final boolean hasBoundary = rowsAnd != 0x3FF; + + long westFace = 0L, eastFace = 0L, northFace = 0L, southFace = 0L; + if (hasBoundary) { + long westOpaque = 0L, eastOpaque = 0L, northOpaque = 0L, southOpaque = 0L; + for (int j = 0; j < CELLS_PER_CHUNK; j++) { + final int rowShift = j * CELLS_PER_CHUNK; + westOpaque |= ((long) (opaqueRows[j + 1] & 0xFF)) << rowShift; + eastOpaque |= ((long) ((opaqueRows[j + 1] >>> 2) & 0xFF)) << rowShift; + northOpaque |= ((long) ((opaqueRows[j] >>> 1) & 0xFF)) << rowShift; + southOpaque |= ((long) ((opaqueRows[j + 2] >>> 1) & 0xFF)) << rowShift; + } + westFace = emittableBits & ~westOpaque; + eastFace = emittableBits & ~eastOpaque; + northFace = emittableBits & ~northOpaque; + southFace = emittableBits & ~southOpaque; + } + + emitZRun(westFace, chunkOffsetX, chunkOffsetZ, y1, scrollX, scrollZ, 0.0, N_NEG_X, false); + emitZRun(eastFace, chunkOffsetX, chunkOffsetZ, y1, scrollX, scrollZ, 1.0 - EDGE_OVERLAP, N_POS_X, true); + emitXRun(northFace, chunkOffsetX, chunkOffsetZ, y1, scrollX, scrollZ, 0.0, N_NEG_Z, false); + emitXRun(southFace, chunkOffsetX, chunkOffsetZ, y1, scrollX, scrollZ, 1.0 - EDGE_OVERLAP, N_POS_Z, true); + } + + private void emitZRun(long faceBits, float chunkOffsetX, float chunkOffsetZ, double y1, float scrollX, float scrollZ, double xOffset, int normalInt, boolean flip) { + while (faceBits != 0L) { + final int bit = Long.numberOfTrailingZeros(faceBits); + final int k = bit & 7; + final int j0 = bit >>> 3; + int h = 1; + while (j0 + h < CELLS_PER_CHUNK && ((faceBits >>> ((j0 + h) * CELLS_PER_CHUNK + k)) & 1L) != 0L) h++; + long mask = 0L; + for (int dh = 0; dh < h; dh++) mask |= 1L << ((j0 + dh) * CELLS_PER_CHUNK + k); + faceBits &= ~mask; + + final double x = chunkOffsetX + k + xOffset; + final double z0 = chunkOffsetZ + j0; + final double z1 = chunkOffsetZ + j0 + h; + final float uC = (chunkOffsetX + k + 0.5F) * SCROLL_SPEED + scrollX; + final float vN = (chunkOffsetZ + j0) * SCROLL_SPEED + scrollZ; + final float vS = (chunkOffsetZ + j0 + h) * SCROLL_SPEED + scrollZ; + final double za = flip ? z0 : z1; + final double zb = flip ? z1 : z0; + final float va = flip ? vN : vS; + final float vb = flip ? vS : vN; + + ensureEmitCapacity(QUAD_BYTES); + emitCloudQuad(x, 0.0, za, uC, va, x, y1, za, uC, va, x, y1, zb, uC, vb, x, 0.0, zb, uC, vb, F_X, normalInt); + } + } + + private void emitXRun(long faceBits, float chunkOffsetX, float chunkOffsetZ, double y1, float scrollX, float scrollZ, double zOffset, int normalInt, boolean flip) { + while (faceBits != 0L) { + final int bit = Long.numberOfTrailingZeros(faceBits); + final int k0 = bit & 7; + final int j = bit >>> 3; + int w = 1; + while (k0 + w < CELLS_PER_CHUNK && ((faceBits >>> (j * CELLS_PER_CHUNK + k0 + w)) & 1L) != 0L) w++; + faceBits &= ~(((1L << w) - 1L) << (j * CELLS_PER_CHUNK + k0)); + + final double x0 = chunkOffsetX + k0; + final double x1 = chunkOffsetX + k0 + w; + final double z = chunkOffsetZ + j + zOffset; + final float uL = (chunkOffsetX + k0) * SCROLL_SPEED + scrollX; + final float uR = (chunkOffsetX + k0 + w) * SCROLL_SPEED + scrollX; + final float vC = (chunkOffsetZ + j + 0.5F) * SCROLL_SPEED + scrollZ; + final double ya = flip ? 0.0 : y1; + final double yb = flip ? y1 : 0.0; + + ensureEmitCapacity(QUAD_BYTES); + emitCloudQuad(x0, ya, z, uL, vC, x1, ya, z, uR, vC, x1, yb, z, uR, vC, x0, yb, z, uL, vC, F_Z, normalInt); + } + } + + private void emitTopBottomGlobalGreedy(boolean emitTopFace, boolean emitBottomFace, double ye, float scrollX, float scrollZ) { + final int rows = this.gridRows; + final int cols = this.gridCols; + final int lpr = this.gridLongsPerRow; + final int minX = this.gridMinX; + final int minZ = this.gridMinZ; + final long[] todo = this.globalEmitTodo; + System.arraycopy(this.globalEmit, 0, todo, 0, rows * lpr); + + final int bytesPerCell = (emitTopFace ? QUAD_BYTES : 0) + (emitBottomFace ? QUAD_BYTES : 0); + + for (int row = 0; row < rows; row++) { + final int rowOff = row * lpr; + for (int wi = 0; wi < lpr; wi++) { + long word = todo[rowOff + wi]; + while (word != 0L) { + final int bit = Long.numberOfTrailingZeros(word); + final int col0 = wi * 64 + bit; + if (col0 >= cols) break; + + final int w = measureRowRun(todo, rowOff, lpr, cols, col0); + final int wiStart = col0 >>> 6; + final int wiLast = (col0 + w - 1) >>> 6; + final int spanWords = wiLast - wiStart + 1; + if (runMaskScratch.length < spanWords) { + runMaskScratch = new long[Math.max(spanWords, runMaskScratch.length * 2)]; + } + final long[] mask = runMaskScratch; + for (int s = 0; s < spanWords; s++) { + final int wIdx = wiStart + s; + final int wordColStart = wIdx * 64; + final int loBit = Math.max(0, col0 - wordColStart); + final int hiBit = Math.min(63, col0 + w - 1 - wordColStart); + final int bitsInWord = hiBit - loBit + 1; + mask[s] = (bitsInWord == 64) ? -1L : (((1L << bitsInWord) - 1L) << loBit); + } + + int h = 1; + extendRow: + while (row + h < rows) { + final int rOff = (row + h) * lpr; + for (int s = 0; s < spanWords; s++) { + final long m = mask[s]; + if ((todo[rOff + wiStart + s] & m) != m) break extendRow; + } + h++; + } + for (int rr = 0; rr < h; rr++) { + final int rOff = (row + rr) * lpr; + for (int s = 0; s < spanWords; s++) { + todo[rOff + wiStart + s] &= ~mask[s]; + } + } + + final int relX0 = col0 + minX; + final int relX1 = relX0 + w; + final int relZ0 = row + minZ; + final int relZ1 = relZ0 + h; + final double x0 = relX0, x1 = relX1, z0 = relZ0, z1 = relZ1; + final float uL = relX0 * SCROLL_SPEED + scrollX; + final float uR = relX1 * SCROLL_SPEED + scrollX; + final float vN = relZ0 * SCROLL_SPEED + scrollZ; + final float vS = relZ1 * SCROLL_SPEED + scrollZ; + + if (bytesPerCell > 0) ensureEmitCapacity(bytesPerCell); + + if (emitTopFace) { + emitCloudQuad(x0, 0.0, z0, uL, vN, x1, 0.0, z0, uR, vN, x1, 0.0, z1, uR, vS, x0, 0.0, z1, uL, vS, F_TOP, N_NEG_Y); + } + if (emitBottomFace) { + emitCloudQuad(x0, ye, z1, uL, vS, x1, ye, z1, uR, vS, x1, ye, z0, uR, vN, x0, ye, z0, uL, vN, F_BOTTOM, N_POS_Y); + } + + word = todo[rowOff + wi]; + } + } + } + } + + private static int measureRowRun(long[] arr, int rowOff, int lpr, int stride, int col0) { + int wi = col0 >>> 6; + final int bit = col0 & 63; + final long highMask = -1L << bit; + final long inv = ~arr[rowOff + wi] & highMask; + if (inv != 0L) { + final int zeroBit = Long.numberOfTrailingZeros(inv); + return Math.min(zeroBit - bit, stride - col0); + } + int run = 64 - bit; + wi++; + while (wi < lpr) { + final long w = arr[rowOff + wi]; + if (w != -1L) { + run += Long.numberOfTrailingZeros(~w); + return Math.min(run, stride - col0); + } + run += 64; + wi++; + } + return Math.min(run, stride - col0); + } + + private void prepareEmitBuffer() { + if (emitBuffer == null) { + emitBufferCapacity = 256 * 1024; + emitBuffer = MemoryUtilities.memRealloc((ByteBuffer) null, emitBufferCapacity); + emitBufferAddr = MemoryUtilities.memAddress(emitBuffer, 0); + } + writeAddr = emitBufferAddr; + writeEnd = emitBufferAddr + emitBufferCapacity; + quadCount = 0; + } + + private void growEmitBuffer(int neededBytes) { + final long oldBytes = writeAddr - emitBufferAddr; + int newCap = emitBufferCapacity; + while (newCap < neededBytes) newCap *= 2; + emitBuffer = MemoryUtilities.memRealloc(emitBuffer, newCap); + emitBufferAddr = MemoryUtilities.memAddress(emitBuffer, 0); + emitBufferCapacity = newCap; + writeAddr = emitBufferAddr + oldBytes; + writeEnd = emitBufferAddr + newCap; + } + + private void ensureEmitCapacity(int bytes) { + if (writeAddr + bytes > writeEnd) { + growEmitBuffer((int) (writeAddr - emitBufferAddr) + bytes); + } + } + + private void emitCloudQuad(double x0, double y0, double z0, float u0, float v0, double x1, double y1, double z1, float u1, float v1, double x2, double y2, double z2, float u2, float v2, double x3, double y3, double z3, float u3, float v3, byte factorIdx, int normalInt) { + if (quadCount == quadFactor.length) { + quadFactor = java.util.Arrays.copyOf(quadFactor, quadFactor.length * 2); + } + quadFactor[quadCount++] = factorIdx; + + final int packedNormal = normalInt | FACTOR_BYTE_SHIFTED[factorIdx]; + long a = writeAddr; + a = writeCloudVertex(a, x0, y0, z0, u0, v0, packedNormal); + a = writeCloudVertex(a, x1, y1, z1, u1, v1, packedNormal); + a = writeCloudVertex(a, x2, y2, z2, u2, v2, packedNormal); + a = writeCloudVertex(a, x3, y3, z3, u3, v3, packedNormal); + writeAddr = a; + } + + private static long writeCloudVertex(long addr, double x, double y, double z, float u, float v, int normalInt) { + MemoryUtilities.memPutFloat(addr, (float) x); + MemoryUtilities.memPutFloat(addr + 4, (float) y); + MemoryUtilities.memPutFloat(addr + 8, (float) z); + MemoryUtilities.memPutFloat(addr + 12, u); + MemoryUtilities.memPutFloat(addr + 16, v); + MemoryUtilities.memPutInt(addr + NORMAL_OFFSET, normalInt); + return addr + VERTEX_STRIDE; + } + + private static int packNormalInt(byte nx, byte ny, byte nz) { + final int x = nx & 0xFF, y = ny & 0xFF, z = nz & 0xFF; + return BIG_ENDIAN ? (x << 24) | (y << 16) | (z << 8) : x | (y << 8) | (z << 16); + } + + private void sortQuadsByFactor() { + final int qc = quadCount; + final int[] cnt = factorQuadCount; + cnt[0] = 0; + cnt[1] = 0; + cnt[2] = 0; + cnt[3] = 0; + final byte[] qf = quadFactor; + for (int q = 0; q < qc; q++) cnt[qf[q]]++; + + final int totalBytes = qc * QUAD_BYTES; + if (sortedBuffer == null || sortedBufferCapacity < totalBytes) { + int rounded = Math.max(totalBytes, 64 * 1024); + int pow2 = Integer.highestOneBit(rounded - 1) << 1; + if (pow2 <= 0) pow2 = rounded; + sortedBuffer = MemoryUtilities.memRealloc(sortedBuffer, pow2); + sortedBufferAddr = MemoryUtilities.memAddress(sortedBuffer, 0); + sortedBufferCapacity = pow2; + } + + final int[] off = factorVertOffset; + off[0] = 0; + off[1] = cnt[0] * 4; + off[2] = (cnt[0] + cnt[1]) * 4; + off[3] = (cnt[0] + cnt[1] + cnt[2]) * 4; + + final long[] dst = factorWriteAddr; + dst[0] = sortedBufferAddr; + dst[1] = dst[0] + (long) cnt[0] * QUAD_BYTES; + dst[2] = dst[1] + (long) cnt[1] * QUAD_BYTES; + dst[3] = dst[2] + (long) cnt[2] * QUAD_BYTES; + + final long src0 = emitBufferAddr; + int q = 0; + while (q < qc) { + final int f = qf[q]; + int runEnd = q + 1; + while (runEnd < qc && qf[runEnd] == f) runEnd++; + final int runLen = runEnd - q; + final long runBytes = (long) runLen * QUAD_BYTES; + MemoryUtilities.memCopy(src0 + (long) q * QUAD_BYTES, dst[f], runBytes); + dst[f] += runBytes; + q = runEnd; + } + + sortedBuffer.position(0).limit(totalBytes); + } + + private void setupCloudTexture() { + final int bound = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); + if (bound != mipmapTexId) { + GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4); + GLStateManager.glGenerateMipmap(GL11.GL_TEXTURE_2D); + GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR); + GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); + GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); + mipmapTexId = bound; + } + if (bound != cloudTexId || edges == null) { + extractCloudEdges(); + cloudTexId = bound; + } + } + + private void extractCloudEdges() { + final int w = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH); + final int h = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT); + if (w <= 0 || h <= 0) return; + final int needed = w * h * 4; + if (edgeStagingBuffer == null || edgeStagingCapacity < needed) { + edgeStagingBuffer = BufferUtils.createByteBuffer(needed); + edgeStagingCapacity = needed; + } else { + edgeStagingBuffer.clear(); + } + final ByteBuffer buf = edgeStagingBuffer; + GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf); + texW = w; + texH = h; + edges = new CloudEdges(w, h, buf); + edgesGeneration++; + invalidateGeometry(); } private void reloadTextures() { - if (mc.renderEngine != null) { - mc.renderEngine.bindTexture(texture); - texW = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH); - texH = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT); + if (mc.renderEngine == null) return; + mc.renderEngine.bindTexture(texture); + extractCloudEdges(); + if (vao != null) { + vao.delete(); + vao = null; } + deleteEbo(); + mipmapTexId = -1; + cloudTexId = -1; } @Override diff --git a/src/main/java/com/gtnewhorizons/angelica/render/CloudUniforms.java b/src/main/java/com/gtnewhorizons/angelica/render/CloudUniforms.java new file mode 100644 index 000000000..53e09296b --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/render/CloudUniforms.java @@ -0,0 +1,78 @@ +package com.gtnewhorizons.angelica.render; + +import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext; +import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v; +import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformInt; +import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f; + +final class CloudUniforms { + static final float ALPHA = 0.8f; + + final GlUniformMatrix4f mvp; + final GlUniformFloat4v colorMult; + final GlUniformFloat4v fogParams; + final GlUniformFloat4v fogColor; + final GlUniformInt fogEnabled; + final GlUniformInt tex; + + private int lastFogEnabled = -1; + private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN; + private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN; + private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN; + private final float[] uploadBuf = new float[4]; + + CloudUniforms(ShaderBindingContext ctx) { + mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new); + colorMult = ctx.bindUniform("u_ColorMult", GlUniformFloat4v::new); + fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new); + fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new); + fogEnabled = ctx.bindUniform("u_FogEnabled", GlUniformInt::new); + tex = ctx.bindUniform("u_Tex", GlUniformInt::new); + } + + void setFogEnabled(boolean enabled) { + final int val = enabled ? 1 : 0; + if (val != lastFogEnabled) { + lastFogEnabled = val; + fogEnabled.setInt(val); + } + } + + void setFogColor(float r, float g, float b) { + if (r != lastFogR || g != lastFogG || b != lastFogB) { + lastFogR = r; + lastFogG = g; + lastFogB = b; + uploadBuf[0] = r; + uploadBuf[1] = g; + uploadBuf[2] = b; + uploadBuf[3] = 1.0f; + fogColor.set(uploadBuf); + } + } + + void setFogParams(float x, float y) { + if (x != lastFogParamX || y != lastFogParamY) { + lastFogParamX = x; + lastFogParamY = y; + uploadBuf[0] = x; + uploadBuf[1] = y; + uploadBuf[2] = 0f; + uploadBuf[3] = 0f; + fogParams.set(uploadBuf); + } + } + + void setColorMult(float r, float g, float b) { + if (r != lastColorR || g != lastColorG || b != lastColorB) { + lastColorR = r; + lastColorG = g; + lastColorB = b; + uploadBuf[0] = r; + uploadBuf[1] = g; + uploadBuf[2] = b; + uploadBuf[3] = ALPHA; + colorMult.set(uploadBuf); + } + } +} diff --git a/src/main/java/net/coderbot/iris/gl/state/StateUpdateNotifiers.java b/src/main/java/net/coderbot/iris/gl/state/StateUpdateNotifiers.java index 6c9add282..1dd1dcafb 100644 --- a/src/main/java/net/coderbot/iris/gl/state/StateUpdateNotifiers.java +++ b/src/main/java/net/coderbot/iris/gl/state/StateUpdateNotifiers.java @@ -11,6 +11,7 @@ public class StateUpdateNotifiers { public static ValueUpdateNotifier alphaFuncNotifier; public static ValueUpdateNotifier alphaTestNotifier; public static ValueUpdateNotifier blendFuncNotifier; + public static ValueUpdateNotifier colorModulatorNotifier; public static ValueUpdateNotifier bindTextureNotifier; public static ValueUpdateNotifier normalTextureChangeNotifier; public static ValueUpdateNotifier specularTextureChangeNotifier; diff --git a/src/main/java/net/coderbot/iris/pipeline/transform/AttributeTransformer.java b/src/main/java/net/coderbot/iris/pipeline/transform/AttributeTransformer.java index 070e6b574..e5dc10b61 100644 --- a/src/main/java/net/coderbot/iris/pipeline/transform/AttributeTransformer.java +++ b/src/main/java/net/coderbot/iris/pipeline/transform/AttributeTransformer.java @@ -34,7 +34,7 @@ class AttributeTransformer { transformer.injectVariable("layout(location = 4) in vec3 iris_Normal;"); transformer.rename("gl_Vertex", "iris_Vertex"); - transformer.rename("gl_Color", "iris_Color"); + transformer.replaceExpression("gl_Color", "(iris_Color * iris_ColorModulator)"); transformer.rename("gl_Normal", "iris_Normal"); // ftransform() = gl_ModelViewProjectionMatrix * gl_Vertex diff --git a/src/main/java/net/coderbot/iris/pipeline/transform/CeleritasTransformer.java b/src/main/java/net/coderbot/iris/pipeline/transform/CeleritasTransformer.java index 223a2b5ae..a8bb4300e 100644 --- a/src/main/java/net/coderbot/iris/pipeline/transform/CeleritasTransformer.java +++ b/src/main/java/net/coderbot/iris/pipeline/transform/CeleritasTransformer.java @@ -34,6 +34,7 @@ class CeleritasTransformer { transformer.injectFunction( "void _celeritas_init() { " + "_vert_init(); " + + "_vert_color = _vert_color * iris_ColorModulator; " + "iris_LightTexCoord = vec4(vec2(_vert_tex_light_coord), 0.0, 1.0); }"); transformer.prependMain("_celeritas_init();"); transformShared(transformer, parameters); diff --git a/src/main/java/net/coderbot/iris/pipeline/transform/CommonTransformer.java b/src/main/java/net/coderbot/iris/pipeline/transform/CommonTransformer.java index cb25a0188..fed34cd02 100644 --- a/src/main/java/net/coderbot/iris/pipeline/transform/CommonTransformer.java +++ b/src/main/java/net/coderbot/iris/pipeline/transform/CommonTransformer.java @@ -20,9 +20,10 @@ public class CommonTransformer { } if (parameters.type == ShaderType.VERTEX) { + root.injectVariable("uniform vec4 iris_ColorModulator;"); root.injectVariable("out vec4 iris_FrontColor;"); root.rename("gl_FrontColor", "iris_FrontColor"); - root.prependMain("iris_FrontColor = vec4(1.0);"); + root.prependMain("iris_FrontColor = iris_ColorModulator;"); } else if (parameters.type == ShaderType.FRAGMENT) { root.injectVariable("in vec4 iris_FrontColor;"); root.rename("gl_Color", "iris_FrontColor"); diff --git a/src/main/java/net/coderbot/iris/pipeline/transform/DHGenericTransformer.java b/src/main/java/net/coderbot/iris/pipeline/transform/DHGenericTransformer.java index 024f711d2..99f928ecf 100644 --- a/src/main/java/net/coderbot/iris/pipeline/transform/DHGenericTransformer.java +++ b/src/main/java/net/coderbot/iris/pipeline/transform/DHGenericTransformer.java @@ -94,7 +94,7 @@ public class DHGenericTransformer { + " float skyLight = (float(uSkyLight) + 0.5) / 16.0;" + " _vert_tex_light_coord = vec2(blockLight, skyLight);" + " dhMaterialId = aMaterial;" - + " _vert_color = iris_color;" + + " _vert_color = iris_color * iris_ColorModulator;" + " }" ); transformer.prependMain("_vert_init();"); diff --git a/src/main/java/net/coderbot/iris/pipeline/transform/DHTerrainTransformer.java b/src/main/java/net/coderbot/iris/pipeline/transform/DHTerrainTransformer.java index 0f9ae6f93..14e75fa61 100644 --- a/src/main/java/net/coderbot/iris/pipeline/transform/DHTerrainTransformer.java +++ b/src/main/java/net/coderbot/iris/pipeline/transform/DHTerrainTransformer.java @@ -86,7 +86,7 @@ public class DHTerrainTransformer { + " _vert_normal = irisNormals[int(irisExtra.y)];" + " dhMaterialId = int(irisExtra.x);" + " _vert_tex_light_coord = vec2((float(lights / 16u) + 0.5) / 16.0, (mod(float(lights), 16.0) + 0.5) / 16.0);" - + " _vert_color = iris_color;" + + " _vert_color = iris_color * iris_ColorModulator;" + " }" ); transformer.prependMain("_vert_init();"); diff --git a/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java b/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java index ed405387b..48196cb9a 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java +++ b/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java @@ -23,7 +23,7 @@ public enum ProgramId { TexturedLit(ProgramGroup.Gbuffers, "textured_lit", Textured), SkyBasic(ProgramGroup.Gbuffers, "skybasic", Basic), SkyTextured(ProgramGroup.Gbuffers, "skytextured", Textured), - Clouds(ProgramGroup.Gbuffers, "clouds", Textured), + Clouds(ProgramGroup.Gbuffers, "clouds", Textured, null, new AlphaTestOverride(new AlphaTest(AlphaTestFunction.GREATER, 0.1F))), Terrain(ProgramGroup.Gbuffers, "terrain", TexturedLit), TerrainSolid(ProgramGroup.Gbuffers, "terrain_solid", Terrain), diff --git a/src/main/java/net/coderbot/iris/uniforms/IrisExclusiveUniforms.java b/src/main/java/net/coderbot/iris/uniforms/IrisExclusiveUniforms.java index c7be0d01a..8a14c2bdc 100644 --- a/src/main/java/net/coderbot/iris/uniforms/IrisExclusiveUniforms.java +++ b/src/main/java/net/coderbot/iris/uniforms/IrisExclusiveUniforms.java @@ -2,6 +2,7 @@ package net.coderbot.iris.uniforms; import com.gtnewhorizons.angelica.compat.mojang.Camera; import com.gtnewhorizons.angelica.compat.mojang.GameModeUtil; +import com.gtnewhorizons.angelica.render.CloudRenderer; import net.coderbot.iris.gl.uniform.UniformHolder; import net.coderbot.iris.gl.uniform.UniformUpdateFrequency; import net.minecraft.client.Minecraft; @@ -40,6 +41,16 @@ public class IrisExclusiveUniforms { uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "eyePosition", IrisExclusiveUniforms::getEyePosition); uniforms.uniform3d(UniformUpdateFrequency.PER_FRAME, "relativeEyePosition", IrisExclusiveUniforms::getRelativeEyePosition); uniforms.uniform4f(UniformUpdateFrequency.PER_TICK, "lightningBoltPosition", IrisExclusiveUniforms::getLightningBoltPosition); + uniforms.uniform1f(UniformUpdateFrequency.PER_FRAME, "cloudTime", IrisExclusiveUniforms::getCloudTime); + } + + private static float getCloudTime() { + final WorldClient level = Minecraft.getMinecraft().theWorld; + if (level == null) return 0f; + final long cycle = (long) CloudRenderer.getCloudTextureWidth() * 400L * CloudRenderer.getScaleMult(); + final long t = level.getTotalWorldTime() % cycle; + final float partial = Minecraft.getMinecraft().timer.renderPartialTicks; + return (t + partial) * 0.03F; } private static float getThunderStrength() { diff --git a/src/main/java/net/coderbot/iris/uniforms/IrisInternalUniforms.java b/src/main/java/net/coderbot/iris/uniforms/IrisInternalUniforms.java index a55628f31..eafe9c7d5 100644 --- a/src/main/java/net/coderbot/iris/uniforms/IrisInternalUniforms.java +++ b/src/main/java/net/coderbot/iris/uniforms/IrisInternalUniforms.java @@ -12,10 +12,19 @@ import static net.coderbot.iris.gl.uniform.UniformUpdateFrequency.PER_FRAME; public class IrisInternalUniforms { private static final Vector4f FOG_COLOR = new Vector4f(); + private static final Vector4f COLOR_MODULATOR = new Vector4f(1f, 1f, 1f, 1f); private IrisInternalUniforms() { } + private static Vector4f getColorModulator() { + return COLOR_MODULATOR.set( + GLStateManager.getShaderColorR(), + GLStateManager.getShaderColorG(), + GLStateManager.getShaderColorB(), + GLStateManager.getShaderColorA()); + } + private static float getEffectiveAlphaRef() { if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) { return -1.0f; @@ -44,5 +53,7 @@ public class IrisInternalUniforms { .uniform1f("iris_currentAlphaTest", IrisInternalUniforms::getEffectiveAlphaRef, StateUpdateNotifiers.alphaTestNotifier) .uniform1f("alphaTestRef", IrisInternalUniforms::getEffectiveAlphaRef, StateUpdateNotifiers.alphaTestNotifier) .uniform1i("iris_currentAlphaFunc", IrisInternalUniforms::getEffectiveAlphaFunc, StateUpdateNotifiers.alphaFuncNotifier); + + uniforms.uniform4f("iris_ColorModulator", IrisInternalUniforms::getColorModulator, StateUpdateNotifiers.colorModulatorNotifier); } } diff --git a/src/main/resources/assets/angelica/shaders/cloud.frag b/src/main/resources/assets/angelica/shaders/cloud.frag index c27e7735c..88e8228a5 100644 --- a/src/main/resources/assets/angelica/shaders/cloud.frag +++ b/src/main/resources/assets/angelica/shaders/cloud.frag @@ -1,11 +1,11 @@ #version 330 core -in vec4 v_Color; -in vec2 v_TexCoord0; +in vec2 v_UV; in float v_FogCoord; +in float v_Factor; -uniform sampler2D u_CloudTexture; -uniform vec3 u_ColorMultiplier; +uniform sampler2D u_Tex; +uniform vec4 u_ColorMult; uniform vec4 u_FogParams; // x: -1/(end-start), y: end/(end-start) uniform vec4 u_FogColor; uniform bool u_FogEnabled; @@ -13,11 +13,10 @@ uniform bool u_FogEnabled; out vec4 fragColor; void main() { - vec4 texColor = texture(u_CloudTexture, v_TexCoord0); - vec4 color = v_Color * texColor; - color.rgb *= u_ColorMultiplier; - - if (color.a < 0.1) discard; + vec4 texel = texture(u_Tex, v_UV); + if (texel.a < 0.1) discard; + vec4 color = texel * u_ColorMult; + color.rgb *= v_Factor; if (u_FogEnabled) { float f = clamp(v_FogCoord * u_FogParams.x + u_FogParams.y, 0.0, 1.0); diff --git a/src/main/resources/assets/angelica/shaders/cloud.vert b/src/main/resources/assets/angelica/shaders/cloud.vert index 917774c96..16a94c5aa 100644 --- a/src/main/resources/assets/angelica/shaders/cloud.vert +++ b/src/main/resources/assets/angelica/shaders/cloud.vert @@ -1,22 +1,18 @@ #version 330 core layout(location = 0) in vec3 a_Position; -layout(location = 1) in vec4 a_Color; -layout(location = 2) in vec2 a_TexCoord0; +layout(location = 2) in vec2 a_UV; +layout(location = 5) in float a_Factor; uniform mat4 u_MVPMatrix; -uniform vec2 u_TexOffset; -uniform vec2 u_FogOrigin; -out vec4 v_Color; -out vec2 v_TexCoord0; +out vec2 v_UV; out float v_FogCoord; +out float v_Factor; void main() { - vec4 pos4 = vec4(a_Position, 1.0); - gl_Position = u_MVPMatrix * pos4; - v_Color = a_Color; - v_TexCoord0 = a_TexCoord0 + u_TexOffset; - vec2 horizDelta = a_Position.xz - u_FogOrigin; - v_FogCoord = length(horizDelta); + gl_Position = u_MVPMatrix * vec4(a_Position, 1.0); + v_UV = a_UV; + v_FogCoord = length(a_Position.xz); + v_Factor = a_Factor; } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java index 7509d6231..6e9268d5d 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java @@ -51,6 +51,8 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe pipeline.set(Iris.getPipelineManager().preparePipeline(Iris.getCurrentDimensionName())); + GLStateManager.setShaderColor(1f, 1f, 1f, 1f); + pipeline.get().beginLevelRendering(); }