Compare commits

...
5 Commits
Author SHA1 Message Date
Jason Mitchell 1babfd968c Properly reset/flush state for crash interceptors (#2133) 2026-09-25 20:43:05 -07:00
Eclipse-Sol 888a4de0c9 Add Galaxy Space compat and fix culling issues (#2131) 2026-09-25 19:18:45 -07:00
DarkShadow44 a23cefba92 Only enable shader DH compat if DH is enabled (#2130) 2026-09-23 17:42:45 -07:00
Eclipse-Sol f2c07dea28 Weather instancing (#2127)
* Initial commit for weather-instancing

* attempt to make the builder less ugly
2026-09-22 19:27:05 -04:00
Jason Mitchell 53776684c8 Properly handle display lists with the push/pop optimizations (#2126) 2026-09-22 09:23:49 -07:00
59 changed files with 2983 additions and 901 deletions

No files matched your search

@@ -62,6 +62,7 @@ public final class SystemProperties {
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
// Debug
@@ -77,6 +78,7 @@ public final class SystemProperties {
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
// Set by us, read by celeritas
@@ -125,6 +127,20 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyWeather {
CLEAR,
RAIN,
THUNDER;
public boolean isRaining() {
return this != CLEAR;
}
public boolean isThundering() {
return this == THUNDER;
}
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
@@ -79,6 +79,8 @@ public class DisplayListManager {
// Display list compilation state (current/active context)
private static int glListMode = 0;
private static int glListId = -1;
private static int recordedStatePushes;
private static final DisplayListCommand POP_STATE = GLStateManager::popState;
private static CommandRecorder currentRecorder = null; // Command recorder (null when not recording)
private static volatile Thread recordingThread = null; // Thread that started recording (for thread-safety)
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
@@ -114,6 +116,7 @@ public class DisplayListManager {
List<AccumulatedDraw> draws,
DisplayListCallback callback,
StackTraceElement[] stackTrace,
int recordedStatePushes,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
List<String> pendingOps,
@@ -451,6 +454,25 @@ public class DisplayListManager {
currentRecorder.writePopAttrib();
}
public static int virtualStateDepth(RecordMode mode) {
return mode == RecordMode.COMPILE ? recordedStatePushes : 0;
}
public static void recordPushState(StateSet set) {
currentRecorder.writeComplexCommand(set.pushCommand);
recordedStatePushes++;
}
public static boolean recordPopStateIfPending() {
if (recordedStatePushes > 0) {
drawBarrier();
currentRecorder.writeComplexCommand(POP_STATE);
recordedStatePushes--;
return true;
}
return false;
}
public static void recordFogf(int pname, float param) {
drawBarrier();
currentRecorder.writeFogf(pname, param);
@@ -775,7 +797,7 @@ public class DisplayListManager {
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
}
@@ -787,6 +809,7 @@ public class DisplayListManager {
currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
transformCallback = new DisplayListCallback();
recordedStatePushes = 0;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
// Initialize debug logging fields (only when logging enabled)
@@ -831,6 +854,12 @@ public class DisplayListManager {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
boolean unbalancedStatePush = false;
while (recordPopStateIfPending()) unbalancedStatePush = true;
if (unbalancedStatePush) {
GLStateManager.warnOnce("unbalanced-state-push", "Display list {} ended with unpopped internal state pushes - closing them", glListId);
}
flushAll();
// Reset back to MODELVIEW (default state)
@@ -921,6 +950,7 @@ public class DisplayListManager {
accumulatedDraws = parentContext.draws;
transformCallback = parentContext.callback;
compilationStackTrace = parentContext.stackTrace;
recordedStatePushes = parentContext.recordedStatePushes;
pendingTransformOps = parentContext.pendingOps;
multMatrixSources = parentContext.matrixSources;
drawRangeSources = parentContext.drawSources;
@@ -946,6 +976,7 @@ public class DisplayListManager {
drawRangeSources = null;
glListId = -1;
glListMode = 0;
recordedStatePushes = 0;
}
public static void abortCompilation() {
@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
public final class GLDebug {
@@ -586,6 +587,13 @@ public final class GLDebug {
}
}
public static void resetGroupStack() {
if (debugState instanceof KHRDebugState khr) {
khr.depth = 0;
Arrays.fill(khr.groupStack, null);
}
}
public static void debugMessage(String message) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.debugMessage(message);
@@ -116,6 +116,7 @@ import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -233,7 +234,7 @@ public class GLStateManager {
public static ContextCapabilities capabilities;
// Software stack depths for FFP emulation
public static final int MAX_ATTRIB_STACK_DEPTH = 16 + 2;
public static final int MAX_ATTRIB_STACK_DEPTH = 32;
public static final int MAX_MODELVIEW_STACK_DEPTH = 32 + 2;
public static final int MAX_PROJECTION_STACK_DEPTH = 4;
public static final int MAX_TEXTURE_STACK_DEPTH = 4;
@@ -470,7 +471,7 @@ public class GLStateManager {
public static void reset() {
final GLContextState glCtx = ctx();
runningSplash = true;
DisplayListManager.abortCompilation();
glCtx.attribDepth = 0;
for (int i = 0; i < glCtx.attribSets.length; i++) {
glCtx.attribSets[i] = null;
@@ -484,8 +485,40 @@ public class GLStateManager {
((CowStateStack<?>) stack).drain();
}
Arrays.fill(glCtx.savedMvGen, 0);
Arrays.fill(glCtx.savedMvLinearGen, 0);
Arrays.fill(glCtx.savedProjGen, 0);
Arrays.fill(glCtx.savedTexMatGen, 0);
Arrays.fill(glCtx.savedLightingGen, 0);
Arrays.fill(glCtx.savedFragmentGen, 0);
Arrays.fill(glCtx.savedColorGen, 0);
Arrays.fill(glCtx.savedNormalGen, 0);
Arrays.fill(glCtx.savedTexCoordGen, 0);
glCtx.modelViewMatrix.clear();
glCtx.projectionMatrix.clear();
glCtx.mvGeneration++;
glCtx.mvLinearGeneration++;
glCtx.projGeneration++;
glCtx.texMatrixGeneration++;
glCtx.lightingGeneration++;
glCtx.colorGeneration++;
glCtx.texGenGeneration++;
glCtx.clipPlaneGeneration++;
glCtx.fragmentGeneration++;
glCtx.programGeneration++;
ShaderManager.bumpNormalGeneration();
ShaderManager.bumpTexCoordGeneration();
glCtx.textures.texMatIdentityGen++;
glCtx.drawFramebuffer = 0;
glCtx.readFramebuffer = 0;
glCtx.drawFramebufferGeneration++;
if (RENDER_BACKEND != null) {
RENDER_BACKEND.invalidateStateMirror();
replayStateToBackend();
}
}
/**
@@ -3731,15 +3764,27 @@ public class GLStateManager {
}
public static void pushState(int mask) {
pushState(StateSet.forMask(mask));
pushStateLive(StateSet.forMask(mask));
}
public static int pushState(StateSet set) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
final int token = ctx().attribDepth + DisplayListManager.virtualStateDepth(mode);
if (token >= MAX_ATTRIB_STACK_DEPTH) {
throw new IllegalStateException("Attrib stack overflow: max depth " + MAX_ATTRIB_STACK_DEPTH + " reached");
}
DisplayListManager.recordPushState(set);
if (mode == RecordMode.COMPILE) {
return token;
}
}
return pushStateLive(set);
}
static int pushStateLive(StateSet set) {
attribPushes++;
final GLContextState glCtx = ctx();
if (set.glMask == 0 && DisplayListManager.getRecordMode() == RecordMode.COMPILE) {
throw new IllegalStateException("Internal state set pushed while compiling a display list");
}
if (glCtx.attribDepth >= MAX_ATTRIB_STACK_DEPTH) {
throw new IllegalStateException("Attrib stack overflow: max depth " + MAX_ATTRIB_STACK_DEPTH + " reached");
}
@@ -3783,7 +3828,10 @@ public class GLStateManager {
public static void popStateTo(int depth) {
final GLContextState glCtx = ctx();
while (glCtx.attribDepth > depth) {
final RecordMode mode = DisplayListManager.getRecordMode();
while (glCtx.attribDepth + DisplayListManager.virtualStateDepth(mode) > depth) {
if (mode == RecordMode.COMPILE && DisplayListManager.recordPopStateIfPending()) continue;
if (mode == RecordMode.COMPILE_AND_EXECUTE) DisplayListManager.recordPopStateIfPending();
popState();
}
}
@@ -3919,6 +3967,18 @@ public class GLStateManager {
final int sampler = samplerUnits.get(i);
if (sampler != 0) RENDER_BACKEND.bindSampler(i, sampler);
}
for (int i = 0; i <= glCtx.maxBoundTextureUnit; i++) {
final TextureBinding b = glCtx.textures.getTextureUnitBindings(i);
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + i);
RENDER_BACKEND.bindTexture(b.getTarget() == 0 ? GL11.GL_TEXTURE_2D : b.getTarget(), b.getBinding());
}
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + getActiveTextureUnit());
RENDER_BACKEND.bindVertexArray(glCtx.boundVAO != 0 ? glCtx.boundVAO : defaultVAO);
RENDER_BACKEND.useProgram(glCtx.activeProgram);
ShaderManager.invalidateBoundProgram();
RENDER_BACKEND.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, glCtx.drawFramebuffer);
RENDER_BACKEND.bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, glCtx.readFramebuffer);
}
private static void applyPolygonOffset(GLContextState glCtx) {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -38,6 +39,7 @@ public final class StateSet {
public final int restore;
public final int bump;
public final boolean blendSnapshot;
public final DisplayListCommand pushCommand;
StateSet(int glMask, long[] members, int restore, int bump, boolean blendSnapshot) {
this.glMask = glMask;
@@ -45,6 +47,7 @@ public final class StateSet {
this.restore = restore;
this.bump = bump;
this.blendSnapshot = blendSnapshot;
this.pushCommand = () -> GLStateManager.pushStateLive(this);
}
public boolean contains(int id) {
@@ -242,6 +242,8 @@ public abstract class RenderBackend {
}
}
public void invalidateStateMirror() {}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
@@ -107,6 +107,12 @@ public final class FFPUniformBlock {
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
public static final int SIZE;
public static final String GLSL_DECL;
@@ -34,6 +34,62 @@ public final class InstancedGlslHelpers {
+ "0.0, 1.0)";
}
// wHalfWidth is vanilla's rainXCoords table.
private static final String WEATHER_PROLOGUE = """
vec2 {p}wColumn = {span}.xy;
float {p}wBottom = {span}.z;
float {p}wTop = {span}.w;
vec2 {p}wLightmap = {params}.xy;
float {p}wHash = {params}.z;
bool {p}wSnow = {params}.w > 0.5;
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
float {p}wInvRadius = {p}WeatherParams0.w;
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
float {p}wPartialTicks = {p}WeatherParams1.z;
float {p}wAge = {p}WeatherParams1.w;
float {p}wRainScroll = {p}WeatherParams2.x;
float {p}wSnowScroll = {p}WeatherParams2.y;
float {p}wStrength = {p}WeatherParams2.z;
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
float {p}wCornerY = mix({p}wBottom, {p}wTop, {corner}.y);
{vertex} = vec4(vec3({p}wCornerXZ.x, {p}wCornerY, {p}wCornerXZ.y) + {p}wTranslate, 1.0);
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
float {p}wScroll;
vec2 {p}wDrift;
float {p}wAlpha;
if ({p}wSnow) {
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
{p}wAlpha = {p}wFade * 0.3 + 0.5;
} else {
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
{p}wDrift = vec2(0.0);
{p}wAlpha = {p}wFade * 0.5 + 0.5;
}
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
String vertex, String color, String tex0, String tex1, String sep) {
return WEATHER_PROLOGUE
.replace("{p}", uniformPrefix)
.replace("{span}", attribPrefix + "InstColumnSpan")
.replace("{jitter}", attribPrefix + "InstJitter")
.replace("{params}", attribPrefix + "InstWeatherParams")
.replace("{corner}", corner)
.replace("{vertex}", vertex)
.replace("{color}", color)
.replace("{tex0}", tex0)
.replace("{tex1}", tex1)
.replace("\n", sep);
}
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
@@ -65,6 +121,11 @@ public final class InstancedGlslHelpers {
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
}
case WEATHER -> {
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
}
}
return decls.toArray(String[]::new);
}
@@ -4,7 +4,8 @@ public enum Instancing {
NONE(""),
TEMPLATE("_instanced"),
CUBE("_cubeinstanced"),
PARTICLE("_particleinstanced");
PARTICLE("_particleinstanced"),
WEATHER("_weatherinstanced");
public static final Instancing[] VALUES = values();
@@ -121,6 +121,12 @@ public final class ShaderManager {
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public static void invalidateBoundProgram() {
final ShaderManager sm = Holder.INSTANCE;
sm.lastBoundProgramId = -1;
sm.currentProgram = null;
}
public void deactivate() {
active = false;
currentProgram = null;
@@ -49,6 +49,7 @@ public class Uniforms {
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
private int weatherGen = -1;
int blockWrites;
int blockSkips;
@@ -192,6 +193,12 @@ public class Uniforms {
stagedMisc++;
dirty = true;
}
if (WeatherParams.generation != weatherGen) {
stageWeatherParams();
weatherGen = WeatherParams.generation;
stagedMisc++;
dirty = true;
}
final int stippleFactor = Math.max(line.getStippleFactor(), 1);
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
if (stipplePacked != lineStipple) {
@@ -379,6 +386,15 @@ public class Uniforms {
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
}
private void stageWeatherParams() {
putVec4(FFPUniformBlock.WEATHER_PARAMS_0, WeatherParams.translateX, WeatherParams.translateY,
WeatherParams.translateZ, WeatherParams.invRadius);
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
WeatherParams.partialTicks, WeatherParams.age);
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
WeatherParams.rainStrength, 0.0f);
}
private void putVec4(int offset, Vector4f v) {
putVec4(offset, v.x, v.y, v.z, v.w);
}
@@ -51,6 +51,13 @@ public final class VertexKey {
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
private static final long INSTANCING_MASK = 0x7L;
static {
if (Instancing.VALUES.length > INSTANCING_MASK + 1) {
throw new IllegalStateException("Instancing has outgrown the VertexKey field; widen INSTANCING_MASK");
}
}
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -98,7 +105,7 @@ public final class VertexKey {
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -245,7 +252,7 @@ public final class VertexKey {
}
static long withInstancing(long packed, Instancing kind) {
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
return (packed & ~(INSTANCING_MASK << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
@@ -21,6 +21,10 @@ public final class VertexShaderGenerator {
emitOutputs(sb, key);
sb.append("void main() {\n");
if (key.instancing() == Instancing.WEATHER) {
sb.append(" ").append(InstancedGlslHelpers.weatherPrologue("u_", "a_", "a_TexCoord0",
"vec4 weatherVertex", "vec4 weatherColor", "vec4 weatherTex0", "vec4 weatherTex1", "\n ")).append('\n');
}
emitPositionTransform(sb, key);
if (key.lightingEnabled()) {
emitNormalTransform(sb, key);
@@ -58,7 +62,7 @@ public final class VertexShaderGenerator {
final Instancing kind = key.instancing();
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (kind == Instancing.PARTICLE) {
if (kind == Instancing.PARTICLE || kind == Instancing.WEATHER) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
} else {
if (key.hasVertexColor()) {
@@ -88,6 +92,7 @@ public final class VertexShaderGenerator {
return switch (key.instancing()) {
case CUBE -> "cubeUv";
case PARTICLE -> PARTICLE_UV;
case WEATHER -> "weatherTex0";
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
};
}
@@ -134,6 +139,8 @@ public final class VertexShaderGenerator {
sb.append(" // Position transform\n");
if (key.instancing() == Instancing.PARTICLE) {
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
} else if (key.instancing() == Instancing.WEATHER) {
sb.append(" vec4 pos4 = weatherVertex;\n");
} else {
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
}
@@ -282,6 +289,7 @@ public final class VertexShaderGenerator {
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
switch (key.instancing()) {
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
case WEATHER -> sb.append(" v_Color = weatherColor;\n");
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
}
@@ -312,6 +320,7 @@ public final class VertexShaderGenerator {
final String lightmap = switch (key.instancing()) {
case PARTICLE, TEMPLATE -> "a_InstLightmap";
case CUBE -> "a_InstLightmapScale.xy";
case WEATHER -> "weatherTex1.xy";
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
};
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
/**
* One rain or snow column per instance.
*/
public final class WeatherInstancedAttribs {
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
public static final int OFFSET_COLUMN_SPAN = 0;
public static final int OFFSET_JITTER = 16;
public static final int OFFSET_PARAMS = 32;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
GLStateManager.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
GLStateManager.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
InstancedAttribs.enableInstanced(LOC_JITTER);
InstancedAttribs.enableInstanced(LOC_PARAMS);
}
private WeatherInstancedAttribs() {}
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* The per-frame half of the rain and snow instancing, set once per weather draw.
*/
public final class WeatherParams {
public static float translateX;
public static float translateY;
public static float translateZ;
public static float invRadius;
public static float cameraFracX;
public static float cameraFracZ;
public static float partialTicks;
public static float age;
public static float rainScroll;
public static float snowScroll;
public static float rainStrength;
public static int generation;
private WeatherParams() {}
public static void set(float translateX, float translateY, float translateZ, float invRadius,
float cameraFracX, float cameraFracZ, float partialTicks, float age,
float rainScroll, float snowScroll, float rainStrength) {
WeatherParams.translateX = translateX;
WeatherParams.translateY = translateY;
WeatherParams.translateZ = translateZ;
WeatherParams.invRadius = invRadius;
WeatherParams.cameraFracX = cameraFracX;
WeatherParams.cameraFracZ = cameraFracZ;
WeatherParams.partialTicks = partialTicks;
WeatherParams.age = age;
WeatherParams.rainScroll = rainScroll;
WeatherParams.snowScroll = snowScroll;
WeatherParams.rainStrength = rainStrength;
generation++;
}
}
@@ -0,0 +1,69 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
* the bottom or top edge.
*/
public final class WeatherQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final float[] CORNERS = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
private static int vao;
private static int vbo;
private WeatherQuadMesh() {}
public static int vao() {
if (vao != 0) return vao;
final ByteBuffer data = ByteBuffer.allocateDirect(CORNERS.length * Float.BYTES).order(ByteOrder.nativeOrder());
for (float corner : CORNERS) data.putFloat(corner);
data.flip();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
final int corner = VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation();
GLStateManager.glEnableVertexAttribArray(corner);
GLStateManager.glVertexAttribPointer(corner, 2, GL11.GL_FLOAT, false, 2 * Float.BYTES, 0L);
WeatherInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vao;
}
public static void bindInstanceBuffer(int bufferId) {
GLStateManager.glBindVertexArray(vao());
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
WeatherInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -0,0 +1,305 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@GLCoreTest
public class GLSM_DisplayList_StateSet_GLTest {
private final List<Integer> listsToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
DisplayListManager.abortCompilation();
while (GLStateManager.getAttribDepth() > 0) {
GLStateManager.popState();
}
for (int id : listsToDelete) {
GLStateManager.glDeleteLists(id, 1);
}
listsToDelete.clear();
GLStateManager.disableBlend();
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
private int newList() {
final int id = GLStateManager.glGenLists(1);
listsToDelete.add(id);
return id;
}
private static Int2IntMap commandCounts(int listId) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
assertNotNull(compiled, "compiled display list must exist for id " + listId);
return compiled.getCommandCounts();
}
@Test
void compileBracketDoesNotThrowAndLeavesLiveStateUntouched() {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int func0 = GLStateManager.getAlphaState().getFunction();
final float ref0 = GLStateManager.getAlphaState().getReference();
final int unit0 = GLStateManager.getActiveTextureUnit();
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "cache alpha test enable unchanged after COMPILE-only bracket");
assertEquals(func0, GLStateManager.getAlphaState().getFunction(), "cache alpha func unchanged after COMPILE-only bracket");
assertEquals(ref0, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref unchanged after COMPILE-only bracket");
assertEquals(unit0, GLStateManager.getActiveTextureUnit(), "cache active unit unchanged after COMPILE-only bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after COMPILE-only bracket");
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "push and pop each record one COMPLEX_REF");
}
@Test
void compiledBracketReplayRestoresState() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
GLStateManager.glCallList(list);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored by replay");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored by replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled by replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored by replay");
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored by replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by balanced replay");
}
@Test
void compileAndExecuteBracketIsBalanced() {
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after GL_COMPILE_AND_EXECUTE bracket");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced GL_COMPILE_AND_EXECUTE bracket");
GLStateManager.glCallList(list);
GLStateManager.glCallList(list);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after replay");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replay");
}
@Test
void nestedListsPushInBothLevels() {
GLStateManager.enableBlend();
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
final int depth0 = GLStateManager.getAttribDepth();
final int parent = newList();
final int child = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
final int dParent = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glNewList(child, GL11.GL_COMPILE);
final int dChild = GLStateManager.pushState(StateSet.CULL);
if (cull0) {
GLStateManager.disableCull();
} else {
GLStateManager.enableCull();
}
GLStateManager.popStateTo(dChild);
GLStateManager.glEndList();
GLStateManager.disableBlend();
GLStateManager.popStateTo(dParent);
GLStateManager.glEndList();
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by nested COMPILE-only bracket");
GLStateManager.glCallList(parent);
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the parent BLEND bracket after parent replay");
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after parent replay");
GLStateManager.glCallList(child);
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after child replay");
}
@Test
void nestedCompileAndExecuteChildInsideCompileParent() {
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
final int depth0 = GLStateManager.getAttribDepth();
final int parent = newList();
final int child = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
final int dParent = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glNewList(child, GL11.GL_COMPILE_AND_EXECUTE);
final int dChild = GLStateManager.pushState(StateSet.CULL);
if (cull0) {
GLStateManager.disableCull();
} else {
GLStateManager.enableCull();
}
GLStateManager.popStateTo(dChild);
GLStateManager.glEndList();
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored live immediately after the COMPILE_AND_EXECUTE child bracket closes");
GLStateManager.popStateTo(dParent);
GLStateManager.glEndList();
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced nested bracket");
assertEquals(2, commandCounts(parent).getOrDefault(GLCommand.COMPLEX_REF, 0), "parent list records exactly the BLEND push and pop");
assertEquals(2, commandCounts(child).getOrDefault(GLCommand.COMPLEX_REF, 0), "child list records exactly the CULL push and pop");
}
@Test
void unbalancedBracketIsClosedAtEndList() {
GLStateManager.enableBlend();
final boolean blend0 = GLStateManager.glIsEnabled(GL11.GL_BLEND);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.pushState(StateSet.BATCH);
GLStateManager.disableBlend();
GLStateManager.glEndList();
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "glEndList closes the unbalanced push, recording a matching pop");
GLStateManager.glCallList(list);
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replaying an auto-closed bracket");
verifyIsEnabled(GL11.GL_BLEND, blend0, "blend enable unchanged after replaying an auto-closed bracket");
final int other = newList();
assertDoesNotThrow(() -> {
GLStateManager.glNewList(other, GL11.GL_COMPILE);
GLStateManager.glEndList();
}, "an unrelated list must compile normally after an auto-closed bracket");
}
@Test
void glPushAttribRecordsExactlyOnce() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPopAttrib();
GLStateManager.glEndList();
final Int2IntMap counts = commandCounts(list);
assertEquals(1, counts.getOrDefault(GLCommand.PUSH_ATTRIB, 0), "glPushAttrib records exactly one PUSH_ATTRIB opcode");
assertEquals(1, counts.getOrDefault(GLCommand.POP_ATTRIB, 0), "glPopAttrib records exactly one POP_ATTRIB opcode");
assertEquals(0, counts.getOrDefault(GLCommand.COMPLEX_REF, 0), "glPushAttrib/glPopAttrib never record a COMPLEX_REF");
}
@Test
void livePushStraddlesOpenList() {
GLStateManager.enableBlend();
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glEndList();
GLStateManager.popStateTo(d);
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored to the pre-bracket value");
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the live pop that straddled the open-then-closed list");
}
@Test
void livePopInsideOpenListPopsLiveAndRecordsNothing() {
GLStateManager.enableBlend();
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored by the live pop performed inside the open list");
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored live by the pop of a pre-list push");
assertEquals(0, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "popping a pre-existing live push records nothing into the list");
}
@Test
void virtualDepthOverflowThrowsAtRecordTime() {
final int startDepth = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int pushes = GLStateManager.MAX_ATTRIB_STACK_DEPTH - startDepth;
for (int i = 0; i < pushes; i++) {
GLStateManager.pushState(StateSet.CUTOUT);
}
assertThrows(IllegalStateException.class, () -> GLStateManager.pushState(StateSet.CUTOUT), "virtual depth overflow beyond MAX_ATTRIB_STACK_DEPTH must throw at record time");
DisplayListManager.abortCompilation();
assertEquals(startDepth, GLStateManager.getAttribDepth(), "aborting the overflowed compilation leaves live attrib depth untouched");
}
}
@@ -495,22 +495,6 @@ public class GLSM_StateSet_GLTest {
}
}
@Test
void internalPushDuringDisplayListCompileThrowsAndLeavesDepthUnchanged() {
final int list = GLStateManager.glGenLists(1);
final int depthBefore = GLStateManager.getAttribDepth();
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
assertThrows(IllegalStateException.class, () -> GLStateManager.pushState(StateSet.BLEND));
GLStateManager.pushState(StateSet.forMask(GL11.GL_COLOR_BUFFER_BIT));
GLStateManager.popState();
assertEquals(depthBefore, GLStateManager.getAttribDepth(), "mask-based push/pop is recorded, not executed, and leaves live depth unchanged");
} finally {
GLStateManager.glEndList();
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void allAttribBitsNestedInsideInternalBracketRestoresBothLevels() {
try {
+2 -1
View File
@@ -14,8 +14,9 @@ PLOTS = [
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
"sdl.acquireWaitUs",
"weather.strength", "weather.quads", "weather.drawCalls", "weather.micros",
]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass"]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass", "cl:weather"]
CLOCK = "gl.drawCalls"
@@ -196,6 +196,10 @@ public class SDLGPURenderBackend extends RenderBackend {
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
}
@Override public void invalidateStateMirror() {
s().invalidateMirror();
}
public static final int CTR_SLOT_WRITES = 0;
public static final int CTR_SLOT_WRITES_ELIDED = 1;
public static final int CTR_SSBO_BINDS = 2;
@@ -398,4 +398,50 @@ public final class ContextState {
cachedScissor = SDL_Rect.calloc();
cachedBlendColor = SDL_FColor.calloc();
}
public void invalidateMirror() {
boundFboId = -1;
boundReadFboId = -1;
viewportDirty = true;
scissorDirty = true;
blendColorDirty = true;
lastBoundPipeline = 0;
lastAppliedRenderPassGen = 0;
lastAppliedStencilRef = Integer.MIN_VALUE;
lastBoundEboHandle = 0;
lastBoundEboIndexSize = -1;
lastBoundEboOffset = 0;
lastAppliedSamplerBindGen = -1;
lastAppliedSamplerProgram = 0;
lastAppliedSamplerCb = 0;
lastFragSamplerProgram = 0;
lastVertSamplerProgram = 0;
lastAppliedStorageTexBindGen = -1;
lastAppliedStorageTexProgram = 0;
lastAppliedStorageTexCb = 0;
lastFragStorageTexProgram = 0;
lastVertStorageTexProgram = 0;
lastAppliedStorageBufBindGen = -1;
lastAppliedStorageBufProgram = 0;
lastAppliedStorageBufCb = 0;
lastFragStorageBufProgram = 0;
lastVertStorageBufProgram = 0;
lastAppliedVboBindGen = -1;
lastAppliedVboBindProgram = 0;
lastAppliedVboBindCb = 0;
lastFlushedSamplerBindGen = -1;
lastFlushedProgram = -1;
lastFlushedPendingMutationGen = -1;
lastPushedCbVs = 0;
lastPushedCbFs = 0;
lastPushedProgramVs = -1;
lastPushedProgramFs = -1;
lastPushedUboGenVs = -1;
lastPushedUboGenFs = -1;
pipeline.markInputDirty();
pipeline.markOutputDirty();
pipeline.markShaderDirty();
currentVao.invalidateInputHash();
needsSeed = true;
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.api.clouds;
import net.minecraft.util.ResourceLocation;
public record CloudLayer(
String id,
ResourceLocation texture,
float height,
float cellWidth,
float cellHeight,
float coordinateWidth,
double offsetX,
double offsetZ,
float red,
float green,
float blue,
float alpha) {
public CloudLayer {
if (id == null || id.isBlank()) throw new IllegalArgumentException("Cloud layer id must not be empty");
if (texture == null) throw new IllegalArgumentException("Cloud layer texture must not be null");
if (!Float.isFinite(height)) throw new IllegalArgumentException("Cloud layer height must be finite");
if (!Float.isFinite(cellWidth) || cellWidth <= 0
|| !Float.isFinite(cellHeight) || cellHeight <= 0
|| !Float.isFinite(coordinateWidth) || coordinateWidth <= 0) {
throw new IllegalArgumentException("Cloud layer dimensions must be finite and positive");
}
if (!Double.isFinite(offsetX) || !Double.isFinite(offsetZ)) {
throw new IllegalArgumentException("Cloud layer offsets must be finite");
}
if (!Float.isFinite(red) || red < 0 || !Float.isFinite(green) || green < 0
|| !Float.isFinite(blue) || blue < 0) {
throw new IllegalArgumentException("Cloud layer colors must be finite and nonnegative");
}
if (!Float.isFinite(alpha) || alpha < 0 || alpha > 1) {
throw new IllegalArgumentException("Cloud layer alpha must be between zero and one");
}
}
}
@@ -0,0 +1,10 @@
package com.gtnewhorizons.angelica.api.clouds;
import java.util.List;
import net.minecraft.client.multiplayer.WorldClient;
public interface CloudLayerProvider {
List<CloudLayer> getCloudLayers(WorldClient world, int cloudTicks, float partialTicks);
}
@@ -582,6 +582,17 @@ public class BatchingFontRenderer {
endBatch();
}
public void resetAfterCrash() {
discardDeferredText();
batchDepth = 0;
deferredCmdWatermark = 0;
deferredVertexPos = 0;
deferredIdxPos = 0;
truncateBatchToWatermark();
arenaOwner = null;
flushLastTexture = null;
}
private void deferBatch() {
sealBatchSegment();
if (batchSegments.isEmpty()) {
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
import com.gtnewhorizons.angelica.debug.flyby.FlybyRoute;
import com.gtnewhorizons.angelica.debug.flyby.FlybyRunner;
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.minecraft.client.Minecraft;
@@ -23,7 +24,19 @@ import java.util.List;
public class AngelicaCommand extends CommandBase {
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help");
private static final List<String> SUBCOMMANDS = buildSubcommands();
private static List<String> buildSubcommands() {
final List<String> subcommands = new ArrayList<>(Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help"));
if (isDeobfuscatedEnvironment()) {
subcommands.add("crashtest");
}
return subcommands;
}
private static boolean isDeobfuscatedEnvironment() {
return SystemProperties.isDeobf();
}
@Override
public String getCommandName() {
@@ -32,7 +45,7 @@ public class AngelicaCommand extends CommandBase {
@Override
public String getCommandUsage(ICommandSender sender) {
return "/angelica <wireframe|fog|minimap|flyby|profile|help>";
return "/angelica <wireframe|fog|minimap|flyby|profile" + (isDeobfuscatedEnvironment() ? "|crashtest" : "") + "|help>";
}
@Override
@@ -95,6 +108,13 @@ public class AngelicaCommand extends CommandBase {
case "minimap" -> handleMinimap(sender);
case "flyby" -> handleFlyby(sender, args);
case "profile" -> handleProfile(sender, args);
case "crashtest" -> {
if (isDeobfuscatedEnvironment()) {
handleCrashTest(sender);
} else {
sendHelp(sender);
}
}
default -> sendHelp(sender);
}
}
@@ -119,6 +139,11 @@ public class AngelicaCommand extends CommandBase {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
}
private void handleCrashTest(ICommandSender sender) {
RenderRecovery.armCrashTest();
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Crash test armed: fires on the next tile entity render"));
}
private void handleFlyby(ICommandSender sender, String[] args) {
if (args.length >= 2 && "cancel".equalsIgnoreCase(args[1])) {
FlybyRunner.INSTANCE.cancel();
@@ -221,6 +246,9 @@ public class AngelicaCommand extends CommandBase {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica flyby <" + FlybyRoute.ids() + ">" + EnumChatFormatting.WHITE + " - Run a deterministic benchmark route"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica profile <start|stop|status>" + EnumChatFormatting.WHITE + " - Control async-profiler (JFR) recording"));
if (isDeobfuscatedEnvironment()) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica crashtest" + EnumChatFormatting.WHITE + " - Arm a crash on the next tile entity render"));
}
}
@Override
@@ -1,8 +1,8 @@
package com.gtnewhorizons.angelica.compat.bettercrashes;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderGlobalExt;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import cpw.mods.fml.common.Optional;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
@@ -29,7 +29,6 @@ public class BetterCrashesCompat implements StateManager.IResettable {
Tessellator.instance.isDrawing = false;
AngelicaMod.LOGGER.info("Reloading SodiumRenderer");
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
AngelicaMod.LOGGER.info("Resetting GLStateManager");
GLStateManager.reset();
RenderRecovery.resetAfterCrash();
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.compat.dragonapi;
public final class DragonAPICompat {
private DragonAPICompat() {}
public static boolean forcingSeasonalSnow;
}
@@ -0,0 +1,73 @@
package com.gtnewhorizons.angelica.compat.galaxyspace;
import java.util.ArrayList;
import java.util.List;
import com.gtnewhorizons.angelica.api.clouds.CloudLayer;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.IRenderHandler;
// Compat should live here, not porting all of this to Galaxy Space :trolley:
public final class GalaxySpaceClouds {
private static final String VENUS = "galaxyspace.SolarSystem.planets.venus.dimension.WorldProviderVenus";
private static final String BARNARDA_C = "galaxyspace.BarnardsSystem.planets.barnardaC.dimension.WorldProviderBarnardaC";
private static final String TCETI_E = "galaxyspace.TCetiSystem.planets.tcetiE.dimension.WorldProviderTCetiE";
private static final String VENUS_RENDERER =
"galaxyspace.SolarSystem.planets.venus.dimension.sky.CloudProviderVenus";
private static final String BARNARDA_C_RENDERER =
"galaxyspace.BarnardsSystem.planets.barnardaC.dimension.sky.CloudProviderBarnardaC";
private static final String TCETI_E_RENDERER =
"galaxyspace.TCetiSystem.planets.tcetiE.dimension.sky.CloudProviderTCetiE";
private static final ResourceLocation CLOUD_TEXTURE = new ResourceLocation("textures/environment/clouds.png");
private static final double Z_PHASE = 0.33000001311302185;
private GalaxySpaceClouds() {}
public static boolean supports(WorldClient world, IRenderHandler renderer) {
if (world == null || world.provider == null) return false;
String expected = switch (world.provider.getClass().getName()) {
case VENUS -> VENUS_RENDERER;
case BARNARDA_C -> BARNARDA_C_RENDERER;
case TCETI_E -> TCETI_E_RENDERER;
default -> null;
};
return expected != null && (renderer == null || expected.equals(renderer.getClass().getName()));
}
public static List<CloudLayer> getLayers(WorldClient world, int cloudTicks, float partialTicks) {
String provider = world.provider.getClass().getName();
if (VENUS.equals(provider)) {
return fourLayers("galaxyspace:venus", world.provider.getCloudHeight(), 2.0f, cloudTicks, partialTicks);
}
if (BARNARDA_C.equals(provider)) {
return fourLayers("galaxyspace:barnarda_c", world.provider.getCloudHeight(), 5.0f, cloudTicks, partialTicks);
}
if (TCETI_E.equals(provider)) {
return List.of(new CloudLayer("galaxyspace:tceti_e/0", CLOUD_TEXTURE, world.provider.getCloudHeight(),
12.0f, 4.0f, 12.0f, offsetX(cloudTicks, partialTicks, 0), Z_PHASE,
230.0f / 1200.0f, 230.0f / 1200.0f, 230.0f / 1200.0f, 0.9f));
}
return List.of();
}
private static List<CloudLayer> fourLayers(String id, float baseHeight, float widthStep,
int cloudTicks, float partialTicks) {
List<CloudLayer> layers = new ArrayList<>(4);
for (int i = 0; i < 4; i++) {
float divisor = 1200.0f + 300.0f * i;
layers.add(new CloudLayer(id + "/" + i, CLOUD_TEXTURE, baseHeight + 20.0f * i,
12.0f + widthStep * i, 4.0f, 12.0f, offsetX(cloudTicks, partialTicks, i), Z_PHASE,
234.0f / divisor, 147.0f / divisor, 9.0f / divisor, 0.9f));
}
return layers;
}
private static double offsetX(int cloudTicks, float partialTicks, int layer) {
final double layerFraction = layer / 5.0;
return (cloudTicks * (1.0 - layerFraction) + layerFraction * partialTicks + layer * 250000.0) * 0.03 / 12.0;
}
}
@@ -63,6 +63,10 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableVBOClouds;
@Config.Comment("Replace rain/snow rendering with a cached, instanced version.")
@Config.DefaultBoolean(true)
public static boolean enableInstancedWeather;
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -22,6 +22,7 @@ import net.minecraft.util.ChunkCoordinates;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.MathHelper;
import net.minecraft.world.MinecraftException;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
@@ -215,6 +216,8 @@ public final class FlybyRunner {
final EntityClientPlayerMP player = mc.thePlayer;
if (player == null || mc.theWorld == null) return;
pinClientWeather(mc.theWorld);
switch (this.state) {
case WAITING -> {
if (!this.armed) {
@@ -569,6 +572,13 @@ public final class FlybyRunner {
server.getConfigurationManager().sendChatMsg(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Flyby: world saving is off until you leave this world"));
}
private static void pinClientWeather(World world) {
final SystemProperties.FlybyWeather weather = SystemProperties.FLYBY_WEATHER;
world.rainingStrength = world.prevRainingStrength = weather.isRaining() ? 1.0f : 0.0f;
world.thunderingStrength = world.prevThunderingStrength = weather.isThundering() ? 1.0f : 0.0f;
}
private void applyFreeze(MinecraftServer server) {
for (WorldServer world : server.worldServers) {
if (world == null) continue;
@@ -577,13 +587,17 @@ public final class FlybyRunner {
world.getGameRules().setOrCreateGameRule("doDaylightCycle", "false");
LOGGER.warn("Flyby: doDaylightCycle was on, disabled it");
}
if (world.getWorldInfo().isRaining() || world.getWorldInfo().isThundering()) {
LOGGER.warn("Flyby: weather was active, clearing it");
world.getWorldInfo().setRaining(false);
world.getWorldInfo().setThundering(false);
final SystemProperties.FlybyWeather weather = SystemProperties.FLYBY_WEATHER;
if (world.getWorldInfo().isRaining() != weather.isRaining()
|| world.getWorldInfo().isThundering() != weather.isThundering()) {
LOGGER.warn("Flyby: forcing weather to {}", weather);
world.getWorldInfo().setRaining(weather.isRaining());
world.getWorldInfo().setThundering(weather.isThundering());
}
world.getWorldInfo().setRainTime(Integer.MAX_VALUE);
world.getWorldInfo().setThunderTime(Integer.MAX_VALUE);
world.rainingStrength = world.prevRainingStrength = weather.isRaining() ? 1.0f : 0.0f;
world.thunderingStrength = world.prevThunderingStrength = weather.isThundering() ? 1.0f : 0.0f;
if (world.getWorldTime() % 24000L != SystemProperties.FLYBY_TIME_OF_DAY) {
LOGGER.info("Flyby: freezing time at {}", SystemProperties.FLYBY_TIME_OF_DAY);
@@ -65,6 +65,12 @@ public class HUDCaching {
framebuffer = new SharedDepthFramebuffer(CustomFramebuffer.STENCIL_BUFFER);
}
public static void resetAfterCrash() {
renderingCacheOverride = false;
GLSMConfig.hudCacheOverride = false;
dirty = true;
}
public static void renderCachedHud(EntityRenderer renderer, GuiIngame ingame, float partialTicks, boolean hasScreen, int mouseX, int mouseY) {
ClientEvent.post(ClientEventType.PRE_RENDER_GUI, renderer, partialTicks);
ClientEvent.post(ClientEventType.PRE_RENDER_HUD, renderer, partialTicks);
@@ -99,10 +105,13 @@ public class HUDCaching {
}
renderingCacheOverride = true;
GLSMConfig.hudCacheOverride = true;
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
renderingCacheOverride = false;
GLSMConfig.hudCacheOverride = false;
mc.getFramebuffer().bindFramebuffer(false);
try {
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
} finally {
renderingCacheOverride = false;
GLSMConfig.hudCacheOverride = false;
mc.getFramebuffer().bindFramebuffer(false);
}
} else {
renderer.setupOverlayRendering();
}
@@ -71,6 +71,13 @@ public enum Mixins implements IMixins {
)
),
ANGELICA_WEATHER(new MixinBuilder()
.setPhase(Phase.EARLY)
.addExcludedMod(TargetedMod.DYNAMIC_SURROUNDINGS_MIST)
.addExcludedMod(TargetedMod.DYNAMIC_SURROUNDINGS_ORIGINAL)
.addClientMixins("angelica.rendering.MixinEntityRenderer_Weather")
),
STARMINER_RENDERER_LIVING_ENTITY_OPTIMIZATION(new MixinBuilder()
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.STARMINER)
@@ -505,6 +512,14 @@ public enum Mixins implements IMixins {
)
),
DRAGONAPI_SEASONAL_SNOW(new MixinBuilder("Let DragonAPI's Xmas forced-snow event override weather rendering")
.setPhase(Phase.EARLY)
.addRequiredMod(TargetedMod.DRAGON_API)
.addClientMixins(
"dragonapi.MixinSpecialDayTracker"
)
),
IRIS_RENDERING_NOBACKHAND(new MixinBuilder("Iris Hand Shaders")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris)
@@ -10,4 +10,7 @@ public interface EntityRendererAccessor {
* Get the lightmap texture for shader binding.
*/
DynamicTexture getLightmapTexture();
/** Drives the rain and snow texture scroll. */
int getRendererUpdateCount();
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.render;
import org.joml.Matrix4fc;
/**
* The shape of the drawn disc, how it splits into wedges, and how far out each kind of geometry should
* really be worth building.
@@ -49,6 +51,29 @@ final class CloudDisc {
return (displayHeight * 0.5) / Math.tan(Math.toRadians(fovDegrees) * 0.5);
}
static double pixelsPerRadian(int displayWidth, int displayHeight, Matrix4fc projection) {
if (displayWidth <= 0 || displayHeight <= 0 || projection == null) return 0.0;
final float perspectiveW = projection.m23();
if (!Float.isFinite(perspectiveW) || perspectiveW >= 0.0f || projection.m33() != 0.0f) return 0.0;
final double scale = -0.5 / perspectiveW;
final double a = displayWidth * projection.m00() * scale;
final double b = displayWidth * projection.m10() * scale;
final double c = displayHeight * projection.m01() * scale;
final double d = displayHeight * projection.m11() * scale;
if (!Double.isFinite(a) || !Double.isFinite(b) || !Double.isFinite(c) || !Double.isFinite(d)) {
return 0.0;
}
if (b == 0.0 && c == 0.0) return Math.max(Math.abs(a), Math.abs(d));
final double column0 = a * a + c * c;
final double column1 = b * b + d * d;
final double cross = a * b + c * d;
final double largest = 0.5 * (column0 + column1
+ Math.hypot(column0 - column1, 2.0 * cross));
final double pixels = Math.sqrt(largest);
return Double.isFinite(pixels) ? pixels : 0.0;
}
static int wallCutCells(double pixelsPerRadian, boolean platesInFront) {
if (!platesInFront || pixelsPerRadian <= 0.0) return Integer.MAX_VALUE;
@@ -73,6 +73,27 @@ final class CloudFaceMesh {
interiorCount = 0;
}
void delete() {
if (vao != -1) GLStateManager.glDeleteVertexArrays(vao);
if (vbo != -1) GLStateManager.glDeleteBuffers(vbo);
if (ebo != -1) GLStateManager.glDeleteBuffers(ebo);
vao = -1;
vbo = -1;
ebo = -1;
vboCapacityBytes = 0;
clear();
stampedCount = 0;
ringsPerBucket = 1;
buildAnchorX = Integer.MIN_VALUE;
buildAnchorZ = Integer.MIN_VALUE;
orderAnchorX = Integer.MIN_VALUE;
orderAnchorZ = Integer.MIN_VALUE;
shape = null;
uploadBuffer = null;
Arrays.fill(wallStart, 0);
Arrays.fill(plateStart, 0);
}
void build(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq, int wallCutCells) {
stampedCount = 0;
this.shape = shape;
@@ -0,0 +1,710 @@
/*
* Backported from Minecraft Forge 1.12 under the LGPL 2.1
*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizons.angelica.api.clouds.CloudLayer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.util.MathHelper;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3d;
import org.lwjgl.opengl.GL11;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static com.gtnewhorizons.angelica.render.CloudDisc.CELLS_PER_CHUNK;
import static com.gtnewhorizons.angelica.render.CloudDisc.MARGIN_CELLS;
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGE_COUNT;
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGES_PER_RADIAN;
import static com.gtnewhorizons.angelica.render.CloudDisc.textureOffset;
import static com.gtnewhorizons.angelica.render.CloudDisc.withinMargin;
/**
* Draws a cloud layer.
*
* <h2>Per frame, in {@link #render}</h2>
* <ol>
* <li>Reduce the texture to cells, plate rectangles and wall runs into {@link CloudShape}.
* <li>Convert the camera position to cell space: the anchor cell, the fraction across it, and how far the
* cloud base sits above the camera.
* <li>Work out which faces the view needs: the underside, the top surface, or both when the camera is
* inside the deck.
* <li>Get the two detail distances from {@link CloudDisc}: where walls stop being built, and where
* {@link CloudPlateCover} switches to coarse plate rectangles.
* <li>Choose the mesher: {@link CloudFaceMesh} inside the deck, {@link CloudVertexMesh} elsewhere and
* always for shader packs.
* <li>Pick the matching program depending on whether every solid texel is plain white.
* <li>Rebuild when the cache is stale. {@link CloudVertexMesh} builds on a worker thread.
* <li>Reorder near-to-far on every cell crossed, and keep the cell the camera sits in up to date.
* <li>Upload matrices, fog and the texture scroll into {@link CloudUniforms}, then
* {@link #drawVisibleWedges}.
* </ol>
*/
final class CloudLayerRenderer {
static final int MODE_FAST = 1;
static final int MODE_FANCY = 2;
private static final int UPLOAD_SLICE_BYTES = 256 * 1024;
private static final Tracy.ZoneId Z_RENDER = Tracy.zoneId("cloudRender", Tracy.COLOR_CLIENT);
private static final Tracy.ZoneId Z_SUBMIT = Tracy.zoneId("cloudSubmit", Tracy.COLOR_CLIENT);
private static final Tracy.ZoneId Z_DRAW = Tracy.zoneId("cloudDraw", Tracy.COLOR_CLIENT);
private static final Tracy.ZoneId Z_ASYNC_BUILD = Tracy.zoneId("cloudAsyncBuild", Tracy.COLOR_CLIENT);
private static final Executor BUILD_THREAD = Executors.newSingleThreadExecutor(task -> {
final Thread thread = new Thread(task, "Angelica Cloud Builder");
thread.setDaemon(true);
thread.setPriority(Thread.NORM_PRIORITY - 1);
return thread;
});
private final Minecraft mc = Minecraft.getMinecraft();
private final CloudRenderResources resources;
final CloudRenderResources.Texture texture;
CloudLayer layer;
private final CloudFaceMesh faceMesh = new CloudFaceMesh();
private CloudVertexMesh vertexMesh = new CloudVertexMesh();
private CloudVertexMesh spareMesh = new CloudVertexMesh();
private final CloudVertexMesh interiorMesh = new CloudVertexMesh();
private int interiorAnchorX = Integer.MIN_VALUE, interiorAnchorZ = Integer.MIN_VALUE;
private int interiorShapeGeneration = Integer.MIN_VALUE;
private float interiorCellHeight = Float.NaN;
private boolean interiorShadersActive, interiorActive;
private float interiorDriftX, interiorDriftZ;
private CompletableFuture<Void> buildInFlight;
private CompletableFuture<Void> reorderInFlight;
private CloudVertexMesh reorderMesh;
private BuildParams buildParams;
private BuildParams uploadParams;
private final Matrix4f mvpScratch = new Matrix4f();
private final Matrix4f mvpInteriorScratch = new Matrix4f();
private final Matrix4f modelViewScratch = new Matrix4f();
private CloudShape shape;
private int shapeGeneration;
private boolean faceMeshActive;
private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE;
private int cachedShapeGeneration = Integer.MIN_VALUE;
private boolean cachedEmitUnderside, cachedEmitTopSurface;
private int cachedRenderRadiusChunks = -1;
private float cachedCellHeightBlocks = Float.NaN;
private boolean cachedShadersActive;
private int cachedWallCutCells = -1;
private int cachedPlateLodCells = -1;
private boolean geometryBuilt;
private int wallCutCells = Integer.MAX_VALUE;
private int plateLodCells = Integer.MAX_VALUE;
private CloudView cloudView;
private int cloudMode = -1, renderDistance = -1, scaleMult = -1;
private float alpha;
private double driftCells;
private double cullingHeightCells;
private int arcFrom, arcTo;
private int drawCalls;
private GlProgram<CloudUniforms> program;
CloudLayerRenderer(CloudRenderResources resources, CloudRenderResources.Texture texture) {
this.resources = resources;
this.texture = texture;
}
void configure(CloudLayer layer, int mode, int distance, int scale) {
this.layer = layer;
if (cloudMode != mode || renderDistance != distance || scaleMult != scale) {
invalidateGeometry();
}
cloudMode = mode;
renderDistance = distance;
scaleMult = scale;
alpha = layer.alpha();
}
private void invalidateGeometry() {
anchorCellX = Integer.MIN_VALUE;
anchorCellZ = Integer.MIN_VALUE;
cachedRenderRadiusChunks = -1;
cachedShapeGeneration = Integer.MIN_VALUE;
cachedCellHeightBlocks = Float.NaN;
cancelAsyncBuild();
vertexMesh.clear();
spareMesh.clear();
interiorMesh.clear();
interiorAnchorX = Integer.MIN_VALUE;
interiorAnchorZ = Integer.MIN_VALUE;
interiorActive = false;
faceMesh.clear();
geometryBuilt = false;
}
private boolean untextured() {
return cloudMode == MODE_FANCY && shape != null && shape.opaqueTexelsAllWhite;
}
private void uploadFogUniforms(CloudUniforms uniforms, float fogStart, float fogEnd) {
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
uniforms.setFogEnabled(fogEnabled);
if (!fogEnabled) return;
final FogState fog = GLStateManager.getFogState();
final Vector3d color = fog.getFogColor();
uniforms.setFogColor((float) color.x, (float) color.y, (float) color.z);
switch (fog.getFogMode()) {
case GL11.GL_LINEAR -> {
final float range = fogEnd - fogStart;
uniforms.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? fogEnd / range : 1.0f, 0.0f, 0.0f);
}
case GL11.GL_EXP -> uniforms.setFogParams(0.0f, 0.0f, fog.getDensity(), 1.0f);
default -> uniforms.setFogParams(0.0f, 0.0f, fog.getDensity(), 2.0f);
}
}
boolean render(float partialTicks, CloudView cloudView) {
this.cloudView = cloudView;
if (!Tracy.ENABLED) return renderInner(partialTicks);
Tracy.beginZone(Z_RENDER);
try {
return renderInner(partialTicks);
} finally {
Tracy.endZone();
}
}
private boolean renderInner(float partialTicks) {
texture.bind();
shape = texture.shape;
shapeGeneration = texture.generation;
if (shape == null) return false;
final Entity viewEntity = mc.renderViewEntity;
final float cellWidthBlocks = layer.cellWidth() * scaleMult;
final float cellHeightBlocks = layer.cellHeight() * scaleMult;
final float coordinateWidth = layer.coordinateWidth() * scaleMult;
final float cameraY = (float) (viewEntity.lastTickPosY + (viewEntity.posY - viewEntity.lastTickPosY) * partialTicks);
final double cameraCellX = CloudDisc.cellCoordinate(viewEntity.prevPosX + (viewEntity.posX - viewEntity.prevPosX) * partialTicks, coordinateWidth, layer.offsetX());
final double cameraCellZ = CloudDisc.cellCoordinate(viewEntity.prevPosZ + (viewEntity.posZ - viewEntity.prevPosZ) * partialTicks, coordinateWidth, layer.offsetZ());
final float cloudBaseRelativeY = layer.height() - cameraY + 0.33F;
final float cellFractionX = (float) (cameraCellX - MathHelper.floor_double(cameraCellX));
final float cellFractionZ = (float) (cameraCellZ - MathHelper.floor_double(cameraCellZ));
final int anchorX = MathHelper.floor_double(cameraCellX);
final int anchorZ = MathHelper.floor_double(cameraCellZ);
final boolean emitUnderside = cloudBaseRelativeY + cellHeightBlocks >= 0.0F;
final boolean emitTopSurface = cloudBaseRelativeY <= 0.0F;
final boolean insideDeck = emitUnderside && emitTopSurface;
final int renderRadiusChunks = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cellWidthBlocks));
final boolean shadersActive = IrisApi.getInstance().isShaderPackInUse();
final boolean backFaceCulling = cullBackFaces();
final double pixelsPerRadian = cloudView.pixelsPerRadian;
wallCutCells = CloudDisc.wallCutCells(pixelsPerRadian, !(backFaceCulling && insideDeck));
plateLodCells = CloudDisc.plateLodCells(pixelsPerRadian, cloudBaseRelativeY, cellWidthBlocks,
shape.coarsePlateRects != null && shape.opaqueTexelsAllWhite);
final int marginChunks = (MARGIN_CELLS + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
final int radiusChunks = renderRadiusChunks + marginChunks;
final int radiusCells = radiusChunks * CELLS_PER_CHUNK;
faceMeshActive = cloudMode == MODE_FANCY && !shadersActive && insideDeck && radiusCells <= CloudFaceMesh.CELL_LIMIT;
if (!shadersActive) program = resources.program(faceMeshActive, untextured());
final boolean anchorInRange = geometryBuilt
&& withinMargin(anchorX - anchorCellX, anchorZ - anchorCellZ);
final boolean settingsUnchanged = geometryBuilt
&& cachedShapeGeneration == shapeGeneration
&& cachedEmitUnderside == emitUnderside
&& cachedEmitTopSurface == emitTopSurface
&& cachedRenderRadiusChunks == renderRadiusChunks
&& cachedCellHeightBlocks == cellHeightBlocks
&& cachedShadersActive == shadersActive
&& cachedWallCutCells == wallCutCells
&& (faceMeshActive || cachedPlateLodCells == plateLodCells);
final boolean geomCacheValid = anchorInRange && settingsUnchanged;
final boolean canBuildAsync = settingsUnchanged && !faceMeshActive && vertexMesh.built() && CloudDisc.withinDisc((long) anchorX - anchorCellX, (long) anchorZ - anchorCellZ, radiusCells);
final boolean builderBusy = buildInFlight != null || uploadParams != null;
if (!geomCacheValid && !(canBuildAsync && builderBusy)) {
final BuildParams params = captureBuildParams(anchorX, anchorZ, radiusCells, renderRadiusChunks,
cellHeightBlocks, emitUnderside, emitTopSurface, shadersActive, backFaceCulling);
if (canBuildAsync) {
startAsyncBuild(params);
} else {
cancelAsyncBuild();
if (faceMeshActive) {
faceMesh.build(shape, anchorX, anchorZ, radiusCells, radiusCells * radiusCells, wallCutCells);
} else {
buildVertexMesh(vertexMesh, params);
vertexMesh.uploadBuilt();
}
markGeometryCached(params);
}
}
collectFinishedBuild();
final boolean wantInterior = insideDeck && cloudMode == MODE_FANCY && !faceMeshActive;
if (wantInterior && (interiorAnchorX != anchorX || interiorAnchorZ != anchorZ
|| interiorShapeGeneration != shapeGeneration || interiorCellHeight != cellHeightBlocks
|| interiorShadersActive != shadersActive)) {
interiorMesh.buildInterior(shape, anchorX, anchorZ, cellHeightBlocks, textureOffset(anchorX, shape.width), textureOffset(anchorZ, shape.height), untextured() && !shadersActive, shadersActive);
interiorMesh.uploadBuilt();
interiorAnchorX = anchorX;
interiorAnchorZ = anchorZ;
interiorShapeGeneration = shapeGeneration;
interiorCellHeight = cellHeightBlocks;
interiorShadersActive = shadersActive;
}
interiorActive = wantInterior && interiorMesh.built();
if (faceMeshActive) {
if (faceMesh.orderStale(anchorX, anchorZ)) faceMesh.reorder(anchorX, anchorZ);
if (faceMesh.uploadedCount() == 0 && faceMesh.interiorCount() == 0) return true;
} else if (!vertexMesh.built()) {
return true;
}
if (!faceMeshActive && !shadersActive && cloudMode == MODE_FANCY) {
if (reorderInFlight != null && reorderInFlight.isDone()) {
final boolean usable = !reorderInFlight.isCompletedExceptionally() && reorderMesh == vertexMesh;
reorderInFlight = null;
reorderMesh = null;
if (usable) vertexMesh.reorderUpload();
else vertexMesh.discardPendingReorder();
}
if (reorderInFlight == null && vertexMesh.orderStale(anchorX, anchorZ)) {
final CloudVertexMesh target = vertexMesh;
reorderMesh = target;
reorderInFlight = CompletableFuture.runAsync(() -> target.reorder(anchorX, anchorZ), BUILD_THREAD);
}
}
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_SUBMIT);
float r = layer.red();
float g = layer.green();
float b = layer.blue();
if (mc.gameSettings.anaglyph) {
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;
}
final float origFogStart = GLStateManager.getFogState().getStart();
final float origFogEnd = GLStateManager.getFogState().getEnd();
final float cloudExtentBlocks = renderRadiusChunks * CELLS_PER_CHUNK * cellWidthBlocks;
final boolean stretchFog = GLStateManager.getFogMode().isEnabled()
&& GLStateManager.getFogState().getFogMode() == GL11.GL_LINEAR
&& origFogEnd > 0.0f
&& origFogEnd < cloudExtentBlocks;
final float fogStart = stretchFog ? origFogStart * (cloudExtentBlocks / origFogEnd) : origFogStart;
final float fogEnd = stretchFog ? cloudExtentBlocks : origFogEnd;
final boolean pushFog = stretchFog && shadersActive;
if (pushFog) {
GLStateManager.glFogf(GL11.GL_FOG_START, fogStart);
GLStateManager.glFogf(GL11.GL_FOG_END, fogEnd);
}
final float anchorDriftX = (float) (anchorX - anchorCellX);
final float anchorDriftZ = (float) (anchorZ - anchorCellZ);
// Face wedges are re-bucketed around the current cell; vertex wedges keep their build anchor.
final double cameraDriftX = (faceMeshActive ? 0 : anchorDriftX) + cellFractionX + cloudView.offsetX / cellWidthBlocks;
final double cameraDriftZ = (faceMeshActive ? 0 : anchorDriftZ) + cellFractionZ + cloudView.offsetZ / cellWidthBlocks;
driftCells = Math.hypot(cameraDriftX, cameraDriftZ);
final double baseFromEye = cloudBaseRelativeY - cloudView.offsetY;
cullingHeightCells = Math.max(Math.abs(baseFromEye), Math.abs(baseFromEye + cellHeightBlocks)) / cellWidthBlocks;
interiorDriftX = anchorDriftX;
interiorDriftZ = anchorDriftZ;
if (!shadersActive) {
program.bind();
final CloudUniforms uniforms = program.getInterface();
final Matrix4fStack modelView = GLStateManager.getModelViewMatrix();
modelView.pushMatrix();
modelView.scale(cellWidthBlocks, 1.0f, cellWidthBlocks);
if (interiorActive) {
modelView.pushMatrix();
modelView.translate(-cellFractionX, cloudBaseRelativeY, -cellFractionZ);
GLStateManager.getProjectionMatrix().mul(modelView, mvpInteriorScratch);
modelView.popMatrix();
}
modelView.translate(-(cellFractionX + anchorDriftX), cloudBaseRelativeY, -(cellFractionZ + anchorDriftZ));
modelViewScratch.set(modelView);
GLStateManager.getProjectionMatrix().mul(modelView, mvpScratch);
modelView.popMatrix();
uniforms.mvp.set(mvpScratch);
uniforms.modelView.set(modelViewScratch);
if (faceMeshActive) {
uniforms.setCellHeight(cellHeightBlocks);
uniforms.setScroll(textureOffset(faceMesh.buildAnchorX(), shape.width), textureOffset(faceMesh.buildAnchorZ(), shape.height), 1.0f / shape.width, 1.0f / shape.height);
}
uploadFogUniforms(uniforms, fogStart, fogEnd);
drawClouds(r, g, b, false);
program.unbind();
} else {
GLStateManager.glPushMatrix();
GLStateManager.glScalef(cellWidthBlocks, 1.0f, cellWidthBlocks);
GLStateManager.glTranslatef(-(cellFractionX + anchorDriftX), cloudBaseRelativeY, -(cellFractionZ + anchorDriftZ));
drawClouds(r, g, b, true);
GLStateManager.glPopMatrix();
}
if (pushFog) {
GLStateManager.glFogf(GL11.GL_FOG_START, origFogStart);
GLStateManager.glFogf(GL11.GL_FOG_END, origFogEnd);
}
if (Tracy.FINE_ZONES) Tracy.endZone();
return true;
}
private record BuildParams(CloudShape shape, boolean fancy, int anchorX, int anchorZ, int radiusCells,
float cellHeightBlocks, boolean emitUnderside, boolean emitTopSurface, boolean emitPlates,
boolean shadersActive, int wallCutCells, int plateLodCells, boolean untextured,
int shapeGeneration, int renderRadiusChunks) {}
private BuildParams captureBuildParams(int anchorX, int anchorZ, int radiusCells, int renderRadiusChunks,
float cellHeightBlocks, boolean emitUnderside, boolean emitTopSurface,
boolean shadersActive, boolean backFaceCulling) {
return new BuildParams(shape, cloudMode == MODE_FANCY, anchorX, anchorZ, radiusCells, cellHeightBlocks,
emitUnderside, emitTopSurface, !(backFaceCulling && emitUnderside && emitTopSurface), shadersActive,
wallCutCells, plateLodCells, untextured() && !shadersActive, shapeGeneration, renderRadiusChunks);
}
private static void buildVertexMesh(CloudVertexMesh mesh, BuildParams p) {
final float scrollX = textureOffset(p.anchorX(), p.shape().width);
final float scrollZ = textureOffset(p.anchorZ(), p.shape().height);
if (!p.fancy()) {
mesh.buildFast(p.shape(), p.radiusCells(), scrollX, scrollZ, p.shadersActive());
return;
}
mesh.build(p.shape(), p.anchorX(), p.anchorZ(), p.radiusCells(), p.radiusCells() * p.radiusCells(),
p.cellHeightBlocks(), scrollX, scrollZ, p.emitUnderside(), p.emitTopSurface(), p.emitPlates(),
p.wallCutCells(), p.plateLodCells(), p.untextured(), p.shadersActive());
}
private void markGeometryCached(BuildParams p) {
anchorCellX = p.anchorX();
anchorCellZ = p.anchorZ();
cachedShapeGeneration = p.shapeGeneration();
cachedEmitUnderside = p.emitUnderside();
cachedEmitTopSurface = p.emitTopSurface();
cachedRenderRadiusChunks = p.renderRadiusChunks();
cachedCellHeightBlocks = p.cellHeightBlocks();
cachedShadersActive = p.shadersActive();
cachedWallCutCells = p.wallCutCells();
cachedPlateLodCells = p.plateLodCells();
geometryBuilt = true;
}
private void startAsyncBuild(BuildParams p) {
if (buildInFlight != null || uploadParams != null) return;
final CloudVertexMesh target = spareMesh;
buildParams = p;
buildInFlight = CompletableFuture.runAsync(() -> {
if (Tracy.ENABLED) Tracy.beginZone(Z_ASYNC_BUILD);
try {
buildVertexMesh(target, p);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}, BUILD_THREAD);
}
private void collectFinishedBuild() {
if (buildInFlight != null) {
if (!buildInFlight.isDone()) return;
final boolean usable = !buildInFlight.isCompletedExceptionally()
&& buildParams.shapeGeneration() == shapeGeneration;
buildInFlight = null;
if (!usable) {
spareMesh.discardPendingUpload();
buildParams = null;
return;
}
uploadParams = buildParams;
buildParams = null;
if (!spareMesh.beginUpload()) {
swapMeshes();
return;
}
}
if (uploadParams == null) return;
if (!spareMesh.uploadSlice(UPLOAD_SLICE_BYTES)) return;
swapMeshes();
}
private void swapMeshes() {
discardReorder();
final CloudVertexMesh finished = spareMesh;
spareMesh = vertexMesh;
vertexMesh = finished;
markGeometryCached(uploadParams);
uploadParams = null;
}
private void discardReorder() {
if (reorderInFlight != null) {
reorderInFlight.join();
reorderInFlight = null;
}
if (reorderMesh != null) {
reorderMesh.discardPendingReorder();
reorderMesh = null;
}
}
private void cancelAsyncBuild() {
discardReorder();
if (buildInFlight != null) {
buildInFlight.join();
buildInFlight = null;
}
buildParams = null;
uploadParams = null;
spareMesh.discardPendingUpload();
}
private void drawClouds(float r, float g, float b, boolean shadersActive) {
if (Tracy.ENABLED) Tracy.beginZone(Z_DRAW);
try {
drawCloudsInner(r, g, b, shadersActive);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
private void drawCloudsInner(float r, float g, float b, boolean shadersActive) {
drawCalls = 0;
if (faceMeshActive) {
drawFaces(r, g, b);
} else {
drawVertices(r, g, b, shadersActive);
}
}
private void drawFaces(float r, float g, float b) {
GLStateManager.enableCull();
applyAnaglyphColorMask();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(true);
program.getInterface().setColorMult(r, g, b, alpha);
faceMesh.bind();
final int interiorFaces = faceMesh.interiorCount();
if (interiorFaces > 0) {
faceMesh.drawInterior();
drawCalls++;
}
drawVisibleWedges(faceMesh.plateStarts(), faceMesh.wallStarts(), faceMesh.uploadedCount(), true);
faceMesh.unbind();
GLStateManager.glDepthMask(true);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.disableBlend();
}
private void drawVertices(float r, float g, float b, boolean depthPrepass) {
vertexMesh.bind();
final int totalQuads = vertexMesh.quadCount();
if (cullBackFaces()) {
GLStateManager.enableCull();
} else {
GLStateManager.disableCull();
}
if (depthPrepass) {
GLStateManager.disableBlend();
GLStateManager.glDepthMask(true);
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, alpha);
vertexMesh.drawAll();
drawCalls++;
if (interiorActive) {
vertexMesh.unbind();
pushInteriorTransform(true);
interiorMesh.bind();
interiorMesh.drawAll();
interiorMesh.unbind();
popInteriorTransform(true);
vertexMesh.bind();
drawCalls++;
}
}
applyAnaglyphColorMask();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(!depthPrepass);
if (depthPrepass) {
for (int face = 0; face < 4; face++) {
if (vertexMesh.faceEmpty(face)) continue;
final float shade = CloudVertexMesh.FACE_SHADE[face];
GLStateManager.glColor4f(shade * r, shade * g, shade * b, alpha);
vertexMesh.drawFace(face);
drawCalls++;
}
} else {
program.getInterface().setColorMult(r, g, b, alpha);
if (cloudMode == MODE_FANCY && vertexMesh.orderPublished()) {
drawVisibleWedges(vertexMesh.plateStarts(), vertexMesh.wallStarts(), totalQuads, false);
} else {
drawRange(0, totalQuads, false);
}
}
vertexMesh.unbind();
if (interiorActive) {
pushInteriorTransform(depthPrepass);
interiorMesh.bind();
if (depthPrepass) {
for (int face = 0; face < 4; face++) {
if (interiorMesh.faceEmpty(face)) continue;
final float shade = CloudVertexMesh.FACE_SHADE[face];
GLStateManager.glColor4f(shade * r, shade * g, shade * b, alpha);
interiorMesh.drawFace(face);
drawCalls++;
}
} else {
drawCalls++;
interiorMesh.drawRange(0, interiorMesh.quadCount());
}
interiorMesh.unbind();
popInteriorTransform(depthPrepass);
}
GLStateManager.enableCull();
GLStateManager.glDepthMask(true);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.disableBlend();
}
private void pushInteriorTransform(boolean shadersActive) {
if (shadersActive) {
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(interiorDriftX, 0.0f, interiorDriftZ);
} else {
program.getInterface().mvp.set(mvpInteriorScratch);
}
}
private void popInteriorTransform(boolean shadersActive) {
if (shadersActive) {
GLStateManager.glPopMatrix();
} else {
program.getInterface().mvp.set(mvpScratch);
}
}
private boolean cullBackFaces() {
return cloudMode == MODE_FANCY;
}
private void drawVisibleWedges(int[] plateStarts, int[] wallStarts, int total, boolean fromFaceMesh) {
if (!computeVisibleArc()) {
drawRange(0, total, fromFaceMesh);
return;
}
drawRange(0, plateStarts[0], fromFaceMesh);
if (arcFrom <= arcTo) {
drawRange(plateStarts[arcFrom], plateStarts[arcTo + 1], fromFaceMesh);
drawRange(plateStarts[WEDGE_COUNT], wallStarts[0], fromFaceMesh);
drawRange(wallStarts[arcFrom], wallStarts[arcTo + 1], fromFaceMesh);
} else {
drawRange(plateStarts[arcFrom], plateStarts[WEDGE_COUNT], fromFaceMesh);
drawRange(plateStarts[0], plateStarts[arcTo + 1], fromFaceMesh);
drawRange(plateStarts[WEDGE_COUNT], wallStarts[0], fromFaceMesh);
drawRange(wallStarts[arcFrom], wallStarts[WEDGE_COUNT], fromFaceMesh);
drawRange(wallStarts[0], wallStarts[arcTo + 1], fromFaceMesh);
}
}
private void drawRange(int start, int end, boolean fromFaceMesh) {
final int count = end - start;
if (count <= 0) return;
drawCalls++;
if (fromFaceMesh) {
faceMesh.drawRange(start, end);
} else {
vertexMesh.drawRange(start, end);
}
}
private boolean computeVisibleArc() {
final double halfSpan = cloudView.halfSpan(cullingHeightCells, driftCells) * WEDGES_PER_RADIAN + 0.25;
if (halfSpan * 2.0 + 1.0 >= WEDGE_COUNT) return false;
final double centre = (cloudView.facing + Math.PI) * WEDGES_PER_RADIAN;
arcFrom = Math.floorMod((int) Math.floor(centre - halfSpan), WEDGE_COUNT);
arcTo = Math.floorMod((int) Math.ceil(centre + halfSpan), WEDGE_COUNT);
return true;
}
private void applyAnaglyphColorMask() {
if (!mc.gameSettings.anaglyph) {
GLStateManager.glColorMask(true, true, true, true);
return;
}
switch (EntityRenderer.anaglyphField) {
case 0 -> GLStateManager.glColorMask(false, true, true, true);
case 1 -> GLStateManager.glColorMask(true, false, false, true);
default -> GLStateManager.glColorMask(true, true, true, true);
}
}
void delete() {
invalidateGeometry();
vertexMesh.deleteVao();
vertexMesh.deleteEbo();
spareMesh.deleteVao();
spareMesh.deleteEbo();
interiorMesh.deleteVao();
interiorMesh.deleteEbo();
faceMesh.delete();
program = null;
}
}
@@ -0,0 +1,118 @@
package com.gtnewhorizons.angelica.render;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
final class CloudRenderResources {
private final Map<ResourceLocation, Texture> textures = new HashMap<>();
private final Map<Integer, GlProgram<CloudUniforms>> programs = new HashMap<>(4);
Texture texture(ResourceLocation location) {
return textures.computeIfAbsent(location, Texture::new);
}
GlProgram<CloudUniforms> program(boolean faces, boolean untextured) {
int key = (faces ? 2 : 0) | (untextured ? 1 : 0);
return programs.computeIfAbsent(key, ignored -> buildProgram(faces, untextured));
}
void clear() {
for (GlProgram<CloudUniforms> program : programs.values()) program.delete();
programs.clear();
textures.clear();
}
private static GlProgram<CloudUniforms> buildProgram(boolean faces, boolean untextured) {
final ShaderConstants.Builder builder = ShaderConstants.builder();
if (untextured) builder.add("UNTEXTURED");
final ShaderConstants constants = builder.build();
final String vertName = faces ? "angelica:cloud_faces.vert" : "angelica:cloud.vert";
final String fragName = faces ? "angelica:cloud_faces.frag" : "angelica:cloud.frag";
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, vertName, constants);
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragName, constants);
final GlProgram<CloudUniforms> built;
try {
built = GlProgram.builder("angelica:cloud")
.attachShader(vert)
.attachShader(frag)
.link(CloudUniforms::new);
} finally {
vert.delete();
frag.delete();
}
built.bind();
if (!untextured) built.getInterface().textureUnit.setInt(0);
built.unbind();
return built;
}
static final class Texture {
final ResourceLocation location;
CloudShape shape;
int generation;
int width;
private final Minecraft mc = Minecraft.getMinecraft();
private int mipmappedTexId = -1;
private int cloudTexId = -1;
private ByteBuffer texelStagingBuffer;
private Texture(ResourceLocation location) {
this.location = location;
}
void bind() {
if (mc.renderEngine == null) return;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
mc.renderEngine.bindTexture(location);
final int boundTexId = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
if (boundTexId != mipmappedTexId) {
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);
mipmappedTexId = boundTexId;
}
if (boundTexId != cloudTexId || shape == null) {
extractCloudShape();
cloudTexId = boundTexId;
}
}
private void extractCloudShape() {
final int texWidth = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
final int texHeight = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
if (texWidth <= 0 || texHeight <= 0) return;
final int neededBytes = texWidth * texHeight * 4;
if (texelStagingBuffer == null || texelStagingBuffer.capacity() < neededBytes) {
texelStagingBuffer = BufferUtils.createByteBuffer(neededBytes);
} else {
texelStagingBuffer.clear();
}
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,
texelStagingBuffer);
width = texWidth;
shape = new CloudShape(texWidth, texHeight, texelStagingBuffer);
generation++;
}
}
}
File diff suppressed because it is too large. Load diff
@@ -26,7 +26,7 @@ final class CloudUniforms {
private float lastTextureScaleX = Float.NaN, lastTextureScaleZ = Float.NaN;
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 lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN, lastColorA = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN, lastFogParamZ = Float.NaN, lastFogParamW = Float.NaN;
private float lastCellHeight = Float.NaN;
@@ -96,15 +96,16 @@ final class CloudUniforms {
}
}
void setColorMult(float r, float g, float b) {
if (r != lastColorR || g != lastColorG || b != lastColorB) {
void setColorMult(float r, float g, float b, float alpha) {
if (r != lastColorR || g != lastColorG || b != lastColorB || alpha != lastColorA) {
lastColorR = r;
lastColorG = g;
lastColorB = b;
lastColorA = alpha;
vec4Buf[0] = r;
vec4Buf[1] = g;
vec4Buf[2] = b;
vec4Buf[3] = ALPHA;
vec4Buf[3] = alpha;
colorMult.set(vec4Buf);
}
}
@@ -0,0 +1,73 @@
package com.gtnewhorizons.angelica.render;
import org.joml.Matrix4fc;
final class CloudView {
private float lastForwardX = Float.NaN, lastForwardZ = Float.NaN;
double pixelsPerRadian;
double facing;
float offsetX, offsetY, offsetZ;
private double sinHalfFovSquared;
private boolean uncullable;
void update(Matrix4fc projection, Matrix4fc modelView, int width, int height) {
pixelsPerRadian = CloudDisc.pixelsPerRadian(width, height, projection);
final boolean projectionCullable = updateProjection(projection);
final boolean cameraCullable = updateCamera(modelView);
uncullable = !projectionCullable || !cameraCullable;
}
private boolean updateProjection(Matrix4fc projection) {
if (!projection.isFinite() || projection.m23() >= 0.0f || projection.m33() != 0.0f
|| projection.m03() != 0.0f || projection.m13() != 0.0f
|| projection.m30() != 0.0f || projection.m31() != 0.0f
|| projection.m01() != 0.0f || projection.m10() != 0.0f
|| projection.m00() == 0.0f || projection.m11() == 0.0f) return false;
final double tanX = (-projection.m23() + Math.abs(projection.m20())) / Math.abs(projection.m00());
final double tanY = (-projection.m23() + Math.abs(projection.m21())) / Math.abs(projection.m11());
final double tanSquared = tanX * tanX + tanY * tanY;
sinHalfFovSquared = tanSquared / (1.0 + tanSquared);
return Double.isFinite(sinHalfFovSquared);
}
private boolean updateCamera(Matrix4fc modelView) {
offsetX = offsetY = offsetZ = 0.0f;
if (!modelView.isFinite() || modelView.m03() != 0.0f || modelView.m13() != 0.0f
|| modelView.m23() != 0.0f || modelView.m33() != 1.0f || !rigid(modelView)) return false;
final float tx = modelView.m30(), ty = modelView.m31(), tz = modelView.m32();
offsetX = -(modelView.m00() * tx + modelView.m01() * ty + modelView.m02() * tz);
offsetY = -(modelView.m10() * tx + modelView.m11() * ty + modelView.m12() * tz);
offsetZ = -(modelView.m20() * tx + modelView.m21() * ty + modelView.m22() * tz);
if (!Float.isFinite(offsetX) || !Float.isFinite(offsetY) || !Float.isFinite(offsetZ)) return false;
final float forwardX = -modelView.m02(), forwardZ = -modelView.m22();
if (forwardX != lastForwardX || forwardZ != lastForwardZ) {
facing = Math.atan2(forwardZ, forwardX);
lastForwardX = forwardX;
lastForwardZ = forwardZ;
}
return true;
}
double halfSpan(double heightCells, double driftCells) {
final double radius = CloudDisc.ALWAYS_DRAWN_CELLS - 2.0 - driftCells;
if (uncullable || radius <= 0.0 || driftCells * 2.0 >= CloudDisc.ALWAYS_DRAWN_CELLS) return Math.PI;
final double slope = heightCells / radius;
final double spreadSquared = sinHalfFovSquared * (1.0 + slope * slope);
if (spreadSquared >= 1.0) return Math.PI;
return Math.asin(Math.sqrt(spreadSquared)) + CloudDisc.driftAngle(driftCells);
}
private static boolean rigid(Matrix4fc matrix) {
if ((matrix.properties() & Matrix4fc.PROPERTY_ORTHONORMAL) != 0) return true;
final double xx = matrix.m00() * matrix.m00() + matrix.m01() * matrix.m01() + matrix.m02() * matrix.m02();
final double yy = matrix.m10() * matrix.m10() + matrix.m11() * matrix.m11() + matrix.m12() * matrix.m12();
final double zz = matrix.m20() * matrix.m20() + matrix.m21() * matrix.m21() + matrix.m22() * matrix.m22();
final double xy = matrix.m00() * matrix.m10() + matrix.m01() * matrix.m11() + matrix.m02() * matrix.m12();
final double xz = matrix.m00() * matrix.m20() + matrix.m01() * matrix.m21() + matrix.m02() * matrix.m22();
final double yz = matrix.m10() * matrix.m20() + matrix.m11() * matrix.m21() + matrix.m12() * matrix.m22();
return Math.abs(xx - 1.0) < 1.0e-4 && Math.abs(yy - 1.0) < 1.0e-4 && Math.abs(zz - 1.0) < 1.0e-4
&& Math.abs(xy) < 1.0e-4 && Math.abs(xz) < 1.0e-4 && Math.abs(yz) < 1.0e-4;
}
}
@@ -0,0 +1,213 @@
package com.gtnewhorizons.angelica.render;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.world.biome.BiomeGenBase;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Random;
/**
* Per-column rain and snow data, sampled once and reused instead of recomputed for every column of every frame.
*/
public final class WeatherColumns {
private static final float TEMPERATURE_PER_BLOCK = 0.05F / 30.0F;
private static final int TEMPERATURE_REFERENCE_Y = 65;
private static final int REFRESH_TICKS = 20;
private final Random random = new Random();
private int size;
private int[] keyX = new int[0];
private int[] keyZ = new int[0];
private boolean[] present = new boolean[0];
private boolean[] precipitates = new boolean[0];
private boolean[] strikesLightning = new boolean[0];
private int[] height = new int[0];
private int[] hash = new int[0];
private float[] temperatureAtReference = new float[0];
private float[] temperatureBelow65 = new float[0];
private int[] light = new int[0];
private int[] lightY = new int[0];
private float[] jitter = new float[0];
private int refreshCursor;
private int revision;
private int lastTick = Integer.MIN_VALUE;
private int originX = Integer.MIN_VALUE;
private int originZ = Integer.MIN_VALUE;
private WeakReference<WorldClient> lastWorld = new WeakReference<>(null);
public int revision() {
return revision;
}
public void update(WorldClient world, int cameraBlockX, int cameraBlockZ, int radius, int tick) {
final int wanted = radius * 2 + 1;
final boolean resized = wanted != size;
if (resized) resize(wanted);
final boolean worldChanged = lastWorld.get() != world;
if (worldChanged) {
lastWorld = new WeakReference<>(world);
Arrays.fill(present, false);
originX = Integer.MIN_VALUE;
originZ = Integer.MIN_VALUE;
revision++;
}
final int slots = size * size;
if (tick != lastTick) {
lastTick = tick;
final int slice = Math.max(1, slots / REFRESH_TICKS);
for (int i = 0; i < slice; i++) {
if (++refreshCursor >= slots) refreshCursor = 0;
if (present[refreshCursor]) sample(world, keyX[refreshCursor], keyZ[refreshCursor], refreshCursor);
}
}
if (!resized && cameraBlockX == originX && cameraBlockZ == originZ) return;
originX = cameraBlockX;
originZ = cameraBlockZ;
for (int dz = -radius; dz <= radius; dz++) {
for (int dx = -radius; dx <= radius; dx++) {
final int x = cameraBlockX + dx;
final int z = cameraBlockZ + dz;
final int slot = slotOf(x, z);
if (present[slot] && keyX[slot] == x && keyZ[slot] == z) continue;
sample(world, x, z, slot);
}
}
}
public boolean precipitates(int x, int z) {
return precipitates[slotOf(x, z)];
}
public int slotAt(int x, int z) {
return slotOf(x, z);
}
public boolean precipitatesAtSlot(int slot) {
return precipitates[slot];
}
public int heightAtSlot(int slot) {
return height[slot];
}
public int hashAtSlot(int slot) {
return hash[slot];
}
public float jitterAtSlot(int slot, int index) {
return jitter[slot * 4 + index];
}
public float temperatureAtSlot(int slot, int y) {
if (y <= 64) return temperatureBelow65[slot];
return temperatureAtReference[slot] - (y - TEMPERATURE_REFERENCE_Y) * TEMPERATURE_PER_BLOCK;
}
public int lightAtSlot(WorldClient world, int slot, int x, int z, int y) {
if (lightY[slot] != y) {
lightY[slot] = y;
light[slot] = world.getLightBrightnessForSkyBlocks(x, y, z, 0);
}
return light[slot];
}
public boolean covers(int x, int z) {
if (size == 0) return false;
final int slot = slotOf(x, z);
return present[slot] && keyX[slot] == x && keyZ[slot] == z;
}
public boolean strikesLightning(int x, int z) {
return strikesLightning[slotOf(x, z)];
}
public int heightAt(int x, int z) {
return height[slotOf(x, z)];
}
public int hashAt(int x, int z) {
return hash[slotOf(x, z)];
}
public float temperatureAt(int x, int z, int y) {
final int slot = slotOf(x, z);
if (y <= 64) return temperatureBelow65[slot];
return temperatureAtReference[slot] - (y - TEMPERATURE_REFERENCE_Y) * TEMPERATURE_PER_BLOCK;
}
public int lightAt(WorldClient world, int x, int z, int y) {
final int slot = slotOf(x, z);
if (lightY[slot] != y) {
lightY[slot] = y;
light[slot] = world.getLightBrightnessForSkyBlocks(x, y, z, 0);
}
return light[slot];
}
public float jitterAt(int x, int z, int index) {
return jitter[slotOf(x, z) * 4 + index];
}
private int slotOf(int x, int z) {
return Math.floorMod(x, size) * size + Math.floorMod(z, size);
}
private void resize(int wanted) {
size = wanted;
final int slots = wanted * wanted;
keyX = new int[slots];
keyZ = new int[slots];
present = new boolean[slots];
precipitates = new boolean[slots];
strikesLightning = new boolean[slots];
height = new int[slots];
hash = new int[slots];
temperatureAtReference = new float[slots];
temperatureBelow65 = new float[slots];
light = new int[slots];
lightY = new int[slots];
jitter = new float[slots * 4];
refreshCursor = 0;
}
private void sample(WorldClient world, int x, int z, int slot) {
final boolean sameColumn = present[slot] && keyX[slot] == x && keyZ[slot] == z;
final boolean wasPrecipitating = precipitates[slot];
final int wasHeight = height[slot];
keyX[slot] = x;
keyZ[slot] = z;
present[slot] = true;
lightY[slot] = Integer.MIN_VALUE;
final BiomeGenBase biome = world.getBiomeGenForCoords(x, z);
strikesLightning[slot] = biome.canSpawnLightningBolt();
precipitates[slot] = strikesLightning[slot] || biome.getEnableSnow();
if (!sameColumn || precipitates[slot] != wasPrecipitating) revision++;
if (!precipitates[slot]) return;
height[slot] = world.getPrecipitationHeight(x, z);
if (height[slot] != wasHeight) revision++;
temperatureAtReference[slot] = biome.getFloatTemperature(x, TEMPERATURE_REFERENCE_Y, z);
temperatureBelow65[slot] = biome.getFloatTemperature(x, 64, z);
// Vanilla overflows these in int before widening
hash[slot] = x * x * 3121 + x * 45238971 + z * z * 418711 + z * 13761;
random.setSeed((long) x * x * 3121 + x * 45238971L ^ (long) z * z * 418711 + z * 13761L);
final int base = slot * 4;
jitter[base] = random.nextFloat();
jitter[base + 1] = (float) random.nextGaussian();
jitter[base + 2] = random.nextFloat();
jitter[base + 3] = (float) random.nextGaussian();
}
}
@@ -0,0 +1,528 @@
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizons.angelica.compat.dragonapi.DragonAPICompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.ffp.WeatherInstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.WeatherParams;
import com.gtnewhorizons.angelica.glsm.ffp.WeatherQuadMesh;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
import jss.notfine.core.SettingsManager;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.particle.EntityRainFX;
import net.minecraft.client.particle.EntitySmokeFX;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.WorldChunkManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Random;
/**
* Replaces EntityRenderer.renderRainSnow.
*
* <p>The whole grid is one instanced draw per precipitation type. Everything that varies per frame
* (billboard axis, distance falloff, texture scroll, snow drift) happens in the vertex shader, and the
* instance buffer is only rebuilt when the camera changes block or {@link WeatherColumns} actually
* resamples something. Per-column data comes from that cache rather than being recomputed every frame.
*/
public final class WeatherRenderer {
private static final ResourceLocation RAIN_TEXTURE = new ResourceLocation("textures/environment/rain.png");
private static final ResourceLocation SNOW_TEXTURE = new ResourceLocation("textures/environment/snow.png");
private static final int REBUILD_TICKS = 20;
private static final long P_STRENGTH = Tracy.plotHandle("weather.strength");
private static final long P_QUADS = Tracy.plotHandle("weather.quads");
private static final long P_DRAW_CALLS = Tracy.plotHandle("weather.drawCalls");
private static final long P_MICROS = Tracy.plotHandle("weather.micros");
private static final long P_REBUILDS = Tracy.plotHandle("weather.rebuilds");
private static final long P_PARTICLE_MICROS = Tracy.plotHandle("weather.particleMicros");
private static final long P_PARTICLES_SPAWNED = Tracy.plotHandle("weather.particlesSpawned");
private static final WeatherColumns columns = new WeatherColumns();
private static float[] axisX;
private static float[] axisZ;
private static int instanceBuffer;
private static ByteBuffer instanceData;
private static int instanceCapacity;
private static int instanceCount;
private static int rainInstances;
private static int builtRevision = Integer.MIN_VALUE;
private static int builtX = Integer.MIN_VALUE;
private static int builtY = Integer.MIN_VALUE;
private static int builtZ = Integer.MIN_VALUE;
private static int builtRadius = -1;
private static int builtUpdateCount = Integer.MIN_VALUE;
private static int builtCameraY = Integer.MIN_VALUE;
private static boolean rebuiltThisFrame;
private static final int PARTICLE_RADIUS = 10;
private static final Random particleRandom = new Random();
private static int rainSoundCounter;
private WeatherRenderer() {}
/** Replaces EntityRenderer.addRainParticles */
public static boolean spawnRainParticles() {
if (!AngelicaConfig.enableInstancedWeather) return false;
if (SettingsManager.downfallDistance < PARTICLE_RADIUS) return false;
final long startNanos = System.nanoTime();
final Minecraft mc = Minecraft.getMinecraft();
final WorldClient world = mc.theWorld;
if (world.provider.getWeatherRenderer() != null) return false;
float strength = world.getRainStrength(1.0F);
if (!mc.gameSettings.fancyGraphics) strength /= 2.0F;
if (strength == 0.0F) {
Tracy.plot(P_PARTICLE_MICROS, 0.0);
Tracy.plotInt(P_PARTICLES_SPAWNED, 0);
return true;
}
int attempts = (int) (100.0F * strength * strength);
if (mc.gameSettings.particleSetting == 1) attempts >>= 1;
else if (mc.gameSettings.particleSetting == 2) attempts = 0;
final int updateCount = ((EntityRendererAccessor) mc.entityRenderer).getRendererUpdateCount();
particleRandom.setSeed(updateCount * 312987231L);
final EntityLivingBase view = mc.renderViewEntity;
final int originX = MathHelper.floor_double(view.posX);
final int originY = MathHelper.floor_double(view.posY);
final int originZ = MathHelper.floor_double(view.posZ);
double soundX = 0.0D, soundY = 0.0D, soundZ = 0.0D;
int landed = 0;
int spawned = 0;
for (int attempt = 0; attempt < attempts; attempt++) {
final int x = originX + particleRandom.nextInt(PARTICLE_RADIUS) - particleRandom.nextInt(PARTICLE_RADIUS);
final int z = originZ + particleRandom.nextInt(PARTICLE_RADIUS) - particleRandom.nextInt(PARTICLE_RADIUS);
final int height;
final boolean eligible;
if (columns.covers(x, z)) {
height = columns.heightAt(x, z);
eligible = columns.strikesLightning(x, z)
&& height <= originY + PARTICLE_RADIUS && height >= originY - PARTICLE_RADIUS
&& columns.temperatureAt(x, z, height) >= 0.15F;
} else {
height = world.getPrecipitationHeight(x, z);
final BiomeGenBase biome = world.getBiomeGenForCoords(x, z);
eligible = height <= originY + PARTICLE_RADIUS && height >= originY - PARTICLE_RADIUS
&& biome.canSpawnLightningBolt() && biome.getFloatTemperature(x, height, z) >= 0.15F;
}
if (!eligible) continue;
final float offsetX = particleRandom.nextFloat();
final float offsetZ = particleRandom.nextFloat();
final Block block = world.getBlock(x, height - 1, z);
final Material material = block.getMaterial();
if (material == Material.air) continue;
final double dropY = (height + 0.1F) - block.getBlockBoundsMinY();
if (material == Material.lava) {
mc.effectRenderer.addEffect(new EntitySmokeFX(world, x + offsetX, dropY, z + offsetZ, 0.0D, 0.0D, 0.0D));
} else {
landed++;
if (particleRandom.nextInt(landed) == 0) {
soundX = x + offsetX;
soundY = dropY;
soundZ = z + offsetZ;
}
mc.effectRenderer.addEffect(new EntityRainFX(world, x + offsetX, dropY, z + offsetZ));
}
spawned++;
}
Tracy.plotInt(P_PARTICLES_SPAWNED, spawned);
Tracy.plot(P_PARTICLE_MICROS, (System.nanoTime() - startNanos) / 1000.0);
if (landed > 0 && particleRandom.nextInt(3) < rainSoundCounter++) {
rainSoundCounter = 0;
final int playerHeight = columns.covers(originX, originZ) && columns.precipitates(originX, originZ)
? columns.heightAt(originX, originZ)
: world.getPrecipitationHeight(originX, originZ);
if (soundY > view.posY + 1.0D && playerHeight > originY) {
world.playSound(soundX, soundY, soundZ, "ambient.weather.rain", 0.1F, 0.5F, false);
} else {
world.playSound(soundX, soundY, soundZ, "ambient.weather.rain", 0.2F, 1.0F, false);
}
}
return true;
}
public static boolean render(float partialTicks) {
final Minecraft mc = Minecraft.getMinecraft();
final WorldClient world = mc.theWorld;
final long startNanos = System.nanoTime();
if (!AngelicaConfig.enableInstancedWeather) return false;
if (world.provider.getWeatherRenderer() != null) {
Tracy.plot(P_STRENGTH, 0.0);
report(0, 0, startNanos);
return false;
}
if (DragonAPICompat.forcingSeasonalSnow) {
report(0, 0, startNanos);
return false;
}
final float rainStrength = world.getRainStrength(partialTicks);
Tracy.plot(P_STRENGTH, rainStrength);
final int radius = SettingsManager.downfallDistance;
if (rainStrength <= 0.0F || radius <= 0) {
report(0, 0, startNanos);
return true;
}
buildAxisTable();
final EntityLivingBase view = mc.renderViewEntity;
final int entityBlockX = MathHelper.floor_double(view.posX);
final int entityBlockY = MathHelper.floor_double(view.posY);
final int entityBlockZ = MathHelper.floor_double(view.posZ);
final double cameraX = view.lastTickPosX + (view.posX - view.lastTickPosX) * partialTicks;
final double cameraY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partialTicks;
final double cameraZ = view.lastTickPosZ + (view.posZ - view.lastTickPosZ) * partialTicks;
final int cameraBlockY = MathHelper.floor_double(cameraY);
final int updateCount = ((EntityRendererAccessor) mc.entityRenderer).getRendererUpdateCount();
columns.update(world, entityBlockX, entityBlockZ, radius, updateCount);
final DeferredWorldRenderingPipeline deferred = deferredPipeline();
if (deferred != null && !deferred.supportsInstancing(Instancing.WEATHER)) {
renderTessellator(mc, world, view, partialTicks, rainStrength, radius, updateCount,
entityBlockX, entityBlockY, entityBlockZ, cameraX, cameraY, cameraZ, cameraBlockY, startNanos);
return true;
}
renderInstanced(mc, world, view, deferred, partialTicks, rainStrength, radius, updateCount,
entityBlockX, entityBlockY, entityBlockZ, cameraX, cameraY, cameraZ, cameraBlockY, startNanos);
return true;
}
private static DeferredWorldRenderingPipeline deferredPipeline() {
if (!AngelicaConfig.enableIris || !Iris.enabled) return null;
return Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline pipeline
? pipeline : null;
}
private static void renderInstanced(Minecraft mc, WorldClient world, EntityLivingBase view,
DeferredWorldRenderingPipeline deferred, float partialTicks, float rainStrength, int radius,
int updateCount, int entityBlockX, int entityBlockY, int entityBlockZ, double cameraX,
double cameraY, double cameraZ, int cameraBlockY, long startNanos) {
final boolean rebuild = SystemProperties.WEATHER_REBUILD_ALWAYS
|| updateCount - builtUpdateCount >= REBUILD_TICKS
|| columns.revision() != builtRevision
|| entityBlockX != builtX || entityBlockY != builtY || entityBlockZ != builtZ
|| cameraBlockY != builtCameraY
|| radius != builtRadius;
if (rebuild) {
rebuildInstances(world, radius, entityBlockX, entityBlockY, entityBlockZ, cameraBlockY);
builtCameraY = cameraBlockY;
builtRevision = columns.revision();
builtX = entityBlockX;
builtY = entityBlockY;
builtZ = entityBlockZ;
builtRadius = radius;
builtUpdateCount = updateCount;
}
rebuiltThisFrame = rebuild;
if (instanceCount == 0) {
report(0, 0, startNanos);
return;
}
if (deferred != null) {
deferred.rebindCurrentPass();
if (!deferred.hasInstancedVariant(Instancing.WEATHER)) {
renderTessellator(mc, world, view, partialTicks, rainStrength, radius, updateCount,
entityBlockX, entityBlockY, entityBlockZ, cameraX, cameraY, cameraZ, cameraBlockY, startNanos);
return;
}
}
WeatherParams.set((float) (entityBlockX - cameraX), (float) -cameraY, (float) (entityBlockZ - cameraZ),
1.0F / radius,
(float) (view.posX - entityBlockX), (float) (view.posZ - entityBlockZ), partialTicks,
updateCount + partialTicks,
updateCount & 31, updateCount & 511, rainStrength);
mc.entityRenderer.enableLightmap(partialTicks);
GLStateManager.disableCull();
GLStateManager.glNormal3f(0.0F, 1.0F, 0.0F);
GLStateManager.enableBlend();
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
int drawCalls = 0;
drawCalls += drawRange(mc, deferred, RAIN_TEXTURE, 0, rainInstances);
drawCalls += drawRange(mc, deferred, SNOW_TEXTURE, rainInstances, instanceCount - rainInstances);
GLStateManager.enableCull();
GLStateManager.disableBlend();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
mc.entityRenderer.disableLightmap(partialTicks);
report(instanceCount, drawCalls, startNanos);
}
private static int drawRange(Minecraft mc, DeferredWorldRenderingPipeline deferred,
ResourceLocation texture, int first, int count) {
if (count <= 0) return 0;
mc.getTextureManager().bindTexture(texture);
if (deferred != null) {
deferred.rebindCurrentPass();
if (!deferred.hasInstancedVariant(Instancing.WEATHER)) return 0;
deferred.bindInstancedVariant(Instancing.WEATHER);
}
GLStateManager.glBindVertexArray(WeatherQuadMesh.vao());
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceBuffer);
WeatherInstancedAttribs.pointInstanceAttribs((long) first * WeatherInstancedAttribs.STRIDE);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(WeatherQuadMesh.VERTEX_FLAGS);
if (deferred == null) GLStateManager.ffpInstancing = Instancing.WEATHER;
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLE_FAN, 0, WeatherQuadMesh.VERTEX_COUNT, count);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.glBindVertexArray(0);
if (deferred != null) deferred.rebindCurrentPass();
return 1;
}
private static void rebuildInstances(WorldClient world, int radius, int entityBlockX, int entityBlockY,
int entityBlockZ, int cameraBlockY) {
final int slots = (radius * 2 + 1) * (radius * 2 + 1);
final int stride = WeatherInstancedAttribs.STRIDE;
if (instanceCapacity < slots) {
instanceCapacity = slots;
instanceData = ByteBuffer.allocateDirect(slots * stride).order(ByteOrder.nativeOrder());
if (instanceBuffer == 0) instanceBuffer = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceBuffer);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) slots * stride, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
WeatherQuadMesh.bindInstanceBuffer(instanceBuffer);
}
int count = writeColumns(world, radius, entityBlockX, entityBlockY, entityBlockZ, cameraBlockY, true, 0);
rainInstances = count;
count = writeColumns(world, radius, entityBlockX, entityBlockY, entityBlockZ, cameraBlockY, false, count);
instanceCount = count;
if (count == 0) return;
instanceData.position(0);
instanceData.limit(count * stride);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceBuffer);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, instanceData);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
instanceData.clear();
}
private static int writeColumns(WorldClient world, int radius, int entityBlockX, int entityBlockY,
int entityBlockZ, int cameraBlockY, boolean wantRain, int count) {
final int stride = WeatherInstancedAttribs.STRIDE;
final WorldChunkManager chunkManager = world.getWorldChunkManager();
for (int z = entityBlockZ - radius; z <= entityBlockZ + radius; z++) {
for (int x = entityBlockX - radius; x <= entityBlockX + radius; x++) {
final int slot = columns.slotAt(x, z);
if (!columns.precipitatesAtSlot(slot)) continue;
final int precipitationHeight = columns.heightAtSlot(slot);
final int bottom = Math.max(entityBlockY - radius, precipitationHeight);
final int top = Math.max(entityBlockY + radius, precipitationHeight);
if (bottom == top) continue;
final float temperature = columns.temperatureAtSlot(slot, bottom);
final boolean rain = chunkManager.getTemperatureAtHeight(temperature, precipitationHeight) >= 0.15F;
if (rain != wantRain) continue;
final int lightY = Math.max(precipitationHeight, cameraBlockY);
int brightness = columns.lightAtSlot(world, slot, x, z, lightY);
if (!rain) brightness = (brightness * 3 + 15728880) / 4;
final int base = count * stride;
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN, x - entityBlockX);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN + 4, z - entityBlockZ);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN + 8, bottom);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN + 12, top);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER, columns.jitterAtSlot(slot, 0));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER + 4, columns.jitterAtSlot(slot, 1));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER + 8, columns.jitterAtSlot(slot, 2));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER + 12, columns.jitterAtSlot(slot, 3));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS, brightness & 0xFFFF);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS + 4, brightness >> 16 & 0xFFFF);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS + 8, columns.hashAtSlot(slot) & 31);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS + 12, rain ? 0.0F : 1.0F);
count++;
}
}
return count;
}
private static void renderTessellator(Minecraft mc, WorldClient world, EntityLivingBase view,
float partialTicks, float rainStrength, int radius, int updateCount, int entityBlockX,
int entityBlockY, int entityBlockZ, double cameraX, double cameraY, double cameraZ,
int cameraBlockY, long startNanos) {
mc.entityRenderer.enableLightmap(partialTicks);
final float age = updateCount + partialTicks;
GLStateManager.disableCull();
GLStateManager.glNormal3f(0.0F, 1.0F, 0.0F);
GLStateManager.enableBlend();
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
final Tessellator tessellator = Tessellator.instance;
int bound = -1;
int quads = 0;
int drawCalls = 0;
for (int z = entityBlockZ - radius; z <= entityBlockZ + radius; z++) {
for (int x = entityBlockX - radius; x <= entityBlockX + radius; x++) {
if (!columns.precipitates(x, z)) continue;
final int precipitationHeight = columns.heightAt(x, z);
final int bottom = Math.max(entityBlockY - radius, precipitationHeight);
final int top = Math.max(entityBlockY + radius, precipitationHeight);
if (bottom == top) continue;
final int lightY = Math.max(precipitationHeight, cameraBlockY);
final float temperature = columns.temperatureAt(x, z, bottom);
final boolean rain = world.getWorldChunkManager()
.getTemperatureAtHeight(temperature, precipitationHeight) >= 0.15F;
final int axis = (z - entityBlockZ + 16) * 32 + x - entityBlockX + 16;
final float halfX = axisX[axis] * 0.5F;
final float halfZ = axisZ[axis] * 0.5F;
final double offsetX = (x + 0.5F) - view.posX;
final double offsetZ = (z + 0.5F) - view.posZ;
final float distance = MathHelper.sqrt_double(offsetX * offsetX + offsetZ * offsetZ) / radius;
if (rain) {
if (bound != 0) {
if (bound >= 0) {
tessellator.draw();
drawCalls++;
}
bound = 0;
mc.getTextureManager().bindTexture(RAIN_TEXTURE);
tessellator.startDrawingQuads();
}
final float scroll = ((updateCount + columns.hashAt(x, z) & 31) + partialTicks) / 32.0F
* (3.0F + columns.jitterAt(x, z, 0));
tessellator.setBrightness(columns.lightAt(world, x, z, lightY));
tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F,
((1.0F - distance * distance) * 0.5F + 0.5F) * rainStrength);
emit(tessellator, x, z, bottom, top, halfX, halfZ, scroll, 0.0F, 0.0F,
cameraX, cameraY, cameraZ);
} else {
if (bound != 1) {
if (bound == 0) {
tessellator.draw();
drawCalls++;
}
bound = 1;
mc.getTextureManager().bindTexture(SNOW_TEXTURE);
tessellator.startDrawingQuads();
}
final float scroll = ((updateCount & 511) + partialTicks) / 512.0F;
final float driftU = columns.jitterAt(x, z, 0) + age * 0.01F * columns.jitterAt(x, z, 1);
final float driftV = columns.jitterAt(x, z, 2) + age * columns.jitterAt(x, z, 3) * 0.001F;
tessellator.setBrightness((columns.lightAt(world, x, z, lightY) * 3 + 15728880) / 4);
tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F,
((1.0F - distance * distance) * 0.3F + 0.5F) * rainStrength);
emit(tessellator, x, z, bottom, top, halfX, halfZ, scroll, driftU, driftV,
cameraX, cameraY, cameraZ);
}
quads++;
}
}
if (bound >= 0) {
tessellator.draw();
drawCalls++;
}
GLStateManager.enableCull();
GLStateManager.disableBlend();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
mc.entityRenderer.disableLightmap(partialTicks);
report(quads, drawCalls, startNanos);
}
private static void emit(Tessellator tessellator, int x, int z, int bottom, int top, float halfX,
float halfZ, float scroll, float driftU, float driftV, double cameraX, double cameraY, double cameraZ) {
tessellator.setTranslation(-cameraX, -cameraY, -cameraZ);
tessellator.addVertexWithUV(x - halfX + 0.5D, bottom, z - halfZ + 0.5D, driftU, bottom / 4.0F + scroll + driftV);
tessellator.addVertexWithUV(x + halfX + 0.5D, bottom, z + halfZ + 0.5D, 1.0F + driftU, bottom / 4.0F + scroll + driftV);
tessellator.addVertexWithUV(x + halfX + 0.5D, top, z + halfZ + 0.5D, 1.0F + driftU, top / 4.0F + scroll + driftV);
tessellator.addVertexWithUV(x - halfX + 0.5D, top, z - halfZ + 0.5D, driftU, top / 4.0F + scroll + driftV);
tessellator.setTranslation(0.0D, 0.0D, 0.0D);
}
private static void report(int quads, int drawCalls, long startNanos) {
Tracy.plotInt(P_QUADS, quads);
Tracy.plotInt(P_DRAW_CALLS, drawCalls);
Tracy.plotInt(P_REBUILDS, rebuiltThisFrame ? 1 : 0);
Tracy.plot(P_MICROS, (System.nanoTime() - startNanos) / 1000.0);
rebuiltThisFrame = false;
}
private static void buildAxisTable() {
if (axisX != null) return;
axisX = new float[1024];
axisZ = new float[1024];
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++) {
final float dx = x - 16;
final float dz = z - 16;
final float length = MathHelper.sqrt_float(dx * dx + dz * dz);
axisX[z << 5 | x] = -dz / length;
axisZ[z << 5 | x] = dx / length;
}
}
}
public static void clear() {
WeatherQuadMesh.delete();
if (instanceBuffer != 0) {
GLStateManager.glDeleteBuffers(instanceBuffer);
instanceBuffer = 0;
}
instanceData = null;
instanceCapacity = 0;
instanceCount = 0;
rainInstances = 0;
}
}
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBlendScope;
import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
import com.prupe.mcpatcher.ctm.CTMUtils;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.PipelineManager;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
public final class RenderRecovery {
private RenderRecovery() {}
private static boolean crashTestArmed;
public static void resetAfterCrash() {
DisplayListManager.abortCompilation();
final PipelineManager pipelineManager = Iris.getPipelineManagerNullable();
if (pipelineManager != null) {
pipelineManager.destroyPipeline();
}
GLStateManager.reset();
GLDebug.resetGroupStack();
final Minecraft mc = Minecraft.getMinecraft();
resetFont(mc.fontRenderer);
resetFont(mc.standardGalacticFontRenderer);
HUDCaching.resetAfterCrash();
mc.getFramebuffer().bindFramebuffer(false);
TesrLifecycle.reset();
TesrBlendScope.reset();
TesrAttribution.currentRenderable = null;
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
CTMUtils.clearCurrentCompact();
}
private static void resetFont(FontRenderer fr) {
if (fr instanceof FontRendererAccessor a && a.angelica$getBatcher() != null) a.angelica$getBatcher().resetAfterCrash();
}
public static void armCrashTest() {
crashTestArmed = true;
}
public static void throwIfCrashTestArmed() {
if (crashTestArmed) {
crashTestArmed = false;
throw new IllegalStateException("angelica crashtest");
}
}
}
@@ -53,6 +53,7 @@ import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
import com.gtnewhorizons.angelica.render.WeatherRenderer;
import com.gtnewhorizons.angelica.rendering.culling.GpuTerrainCuller;
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
@@ -168,6 +169,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
ShadowRenderer.visibleTileEntities.clear();
ShadowRenderer.globalTileEntities.clear();
TesrLifecycle.reset();
WeatherRenderer.clear();
GpuCulling.onWorldUnload();
FfpExtendedAttribs.reset();
FogBiomeCache.invalidate();
@@ -495,6 +495,7 @@ public final class ModelPartBatcher {
groups.clear();
shadowGroups.clear();
templates.clear();
bufferSource.discard();
bufferSource.freeBuffers();
active = false;
shaderPipeline = null;
@@ -118,6 +118,7 @@ public final class TesrBatchRenderer {
}
private void discardDeferred() {
activePass = -1;
deferredFlushPending = false;
pendingDeferredHook = null;
bufferSource.discard();
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import java.util.Arrays;
public final class TesrBlendScope {
private TesrBlendScope() {}
private static int[] depths = new int[4];
private static int depth;
public static void enter() {
if (depth == depths.length) {
depths = Arrays.copyOf(depths, depth * 2);
}
depths[depth++] = GLStateManager.pushState(StateSet.BLEND);
}
public static void exit() {
if (depth == 0) return;
GLStateManager.popStateTo(depths[--depth]);
}
public static void reset() {
depth = 0;
}
}
@@ -89,7 +89,7 @@ public class StandardMacros {
define(standardDefines, "IS_ANGELICA_SDLGPU");
}
if (DHCompat.isPresent()) {
if (DHCompat.hasRenderingEnabled()) {
define(standardDefines, "DISTANT_HORIZONS");
}
@@ -1468,6 +1468,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
out[Instancing.TEMPLATE.ordinal()] = true;
out[Instancing.CUBE.ordinal()] = AngelicaConfig.cubeInstancingEnabled();
out[Instancing.PARTICLE.ordinal()] = true;
out[Instancing.WEATHER.ordinal()] = true;
return out;
}
@@ -2356,11 +2357,16 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
ProgramId.Particles, ProgramId.ParticlesTrans
};
private static final ProgramId[] WEATHER_PROGRAM_IDS = {
ProgramId.Weather
};
private static ProgramId[] attributeProgramIds(Instancing kind) {
return switch (kind) {
case NONE -> ProgramId.values();
case TEMPLATE, CUBE -> INSTANCED_PROGRAM_IDS;
case PARTICLE -> PARTICLE_PROGRAM_IDS;
case WEATHER -> WEATHER_PROGRAM_IDS;
};
}
@@ -38,6 +38,7 @@ class AttributeTransformer {
final boolean instancedVertex = vertex && parameters.instancing != Instancing.NONE;
final boolean cubeVertex = vertex && parameters.instancing == Instancing.CUBE;
final boolean particleVertex = vertex && parameters.instancing == Instancing.PARTICLE;
final boolean weatherVertex = vertex && parameters.instancing == Instancing.WEATHER;
final boolean matrixVertex = vertex && parameters.instancing.hasInstanceHead();
final boolean wantsMvInverse = matrixVertex && transformer.containsCall("gl_ModelViewMatrixInverse");
CoreTransformHelper.injectMatrixUniforms(transformer, vertex ? parameters.instancing : Instancing.NONE);
@@ -47,7 +48,7 @@ class AttributeTransformer {
aliasIfUsed(transformer, "normalMatrix", "iris_NormalMatrix");
if (parameters.type == ShaderType.VERTEX) {
if (!particleVertex) {
if (!particleVertex && !weatherVertex) {
transformer.injectVariable("layout(location = 0) in vec4 iris_Vertex;");
transformer.injectVariable("layout(location = 1) in vec4 iris_Color;");
if (cubeVertex) {
@@ -109,6 +110,10 @@ class AttributeTransformer {
init.append(" iris_MultiTexCoord0 = ").append(InstancedGlslHelpers.particleUv("iris_InstUv", "iris_ParticleCorner")).append(';');
init.append(" iris_MultiTexCoord1 = vec4(iris_InstLightmap, 0.0, 1.0);");
assignMidTexCoord(transformer, init, "vec4((iris_InstUv.x + iris_InstUv.z) * 0.5, (iris_InstUv.y + iris_InstUv.w) * 0.5, 0.0, 1.0)");
} else if (weatherVertex) {
init.append(InstancedGlslHelpers.weatherPrologue("iris_", "iris_", "iris_WeatherCorner",
"iris_Vertex", "iris_Color", "iris_MultiTexCoord0", "iris_MultiTexCoord1", " "));
assignMidTexCoord(transformer, init, "vec4(0.5, 0.5, 0.0, 1.0)");
} else {
init.append("iris_ModelViewMatrix = ").append(InstancedGlslHelpers.mat4FromRows("iris_InstRow0", "iris_InstRow1", "iris_InstRow2")).append(';');
init.append(" iris_NormalMatrix = ").append(InstancedGlslHelpers.mat3FromRows("iris_InstRow0", "iris_InstRow1", "iris_InstRow2")).append(';');
@@ -35,6 +35,20 @@ class CoreTransformHelper {
transformer.injectVariable("uniform mat4 iris_ModelViewMatrixInverse;");
transformer.injectVariable("uniform mat3 iris_NormalMatrix;");
}
case WEATHER -> {
transformer.injectVariable("layout(location = " + VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation() + ") in vec2 iris_WeatherCorner;");
injectAttributeDecls(transformer, instancing);
transformer.injectVariable("uniform vec4 iris_WeatherParams0;");
transformer.injectVariable("uniform vec4 iris_WeatherParams1;");
transformer.injectVariable("uniform vec4 iris_WeatherParams2;");
transformer.injectVariable("vec4 iris_Vertex;");
transformer.injectVariable("vec4 iris_Color;");
transformer.injectVariable("vec4 iris_MultiTexCoord0;");
transformer.injectVariable("vec4 iris_MultiTexCoord1;");
transformer.injectVariable("uniform mat4 iris_ModelViewMatrix;");
transformer.injectVariable("uniform mat4 iris_ModelViewMatrixInverse;");
transformer.injectVariable("uniform mat3 iris_NormalMatrix;");
}
case TEMPLATE, CUBE -> {
injectAttributeDecls(transformer, instancing);
transformer.injectVariable("mat4 iris_ModelViewMatrix;");
@@ -75,6 +75,7 @@ public final class CommonUniforms {
IdMapUniforms.addEntityIdMapUniforms(uniforms);
FogUniforms.addFogUniforms(uniforms, fogMode);
IrisInternalUniforms.addFogUniforms(uniforms, fogMode);
IrisInternalUniforms.addWeatherUniforms(uniforms);
uniforms.uniform2i("atlasSize", () -> {
final int glId = GLStateManager.getBoundTextureForServerState(0);
@@ -1,6 +1,7 @@
package net.coderbot.iris.uniforms;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.WeatherParams;
import net.coderbot.iris.gl.state.FogMode;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.gl.uniform.DynamicUniformHolder;
@@ -38,6 +39,19 @@ public class IrisInternalUniforms {
return func & 0x7;
}
private static final Vector4f WEATHER_0 = new Vector4f();
private static final Vector4f WEATHER_1 = new Vector4f();
private static final Vector4f WEATHER_2 = new Vector4f();
public static void addWeatherUniforms(DynamicUniformHolder uniforms) {
uniforms.uniform4f(PER_FRAME, "iris_WeatherParams0", () -> WEATHER_0.set(
WeatherParams.translateX, WeatherParams.translateY, WeatherParams.translateZ, WeatherParams.invRadius));
uniforms.uniform4f(PER_FRAME, "iris_WeatherParams1", () -> WEATHER_1.set(
WeatherParams.cameraFracX, WeatherParams.cameraFracZ, WeatherParams.partialTicks, WeatherParams.age));
uniforms.uniform4f(PER_FRAME, "iris_WeatherParams2", () -> WEATHER_2.set(
WeatherParams.rainScroll, WeatherParams.snowScroll, WeatherParams.rainStrength, 0.0f));
}
public static void addFogUniforms(DynamicUniformHolder uniforms, FogMode fogMode) {
uniforms.uniform4f(PER_FRAME, "iris_FogColor", () -> {
final Vector3d color = GLStateManager.getFogState().getFogColor();
@@ -47,7 +47,8 @@ public final class PerFrameUniformBlockHarvester {
private static final Set<String> LIVE_STATE_UNIFORMS = Set.of(
"gbufferModelView", "gbufferModelViewInverse", "gbufferPreviousModelView",
"gbufferProjection", "gbufferProjectionInverse", "gbufferPreviousProjection",
"fogColor", "iris_FogColor", "iris_FogStart", "iris_FogEnd", "iris_FogDensity");
"fogColor", "iris_FogColor", "iris_FogStart", "iris_FogEnd", "iris_FogDensity",
"iris_WeatherParams0", "iris_WeatherParams1", "iris_WeatherParams2");
/**
* @param primary manifest whose entry order defines the block layout
@@ -30,6 +30,9 @@ public abstract class MixinEntityRenderer implements EntityRendererAccessor {
@Accessor("lightmapTexture")
public abstract DynamicTexture getLightmapTexture();
@Accessor("rendererUpdateCount")
public abstract int getRendererUpdateCount();
@Inject(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ActiveRenderInfo;updateRenderInfo(Lnet/minecraft/entity/player/EntityPlayer;Z)V", shift = At.Shift.AFTER))
private void angelica$captureCameraMatrix(float partialTicks, long finishTimeNano, CallbackInfo ci) {
final Minecraft mc = Minecraft.getMinecraft();
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.rendering;
import com.gtnewhorizons.angelica.render.WeatherRenderer;
import net.minecraft.client.renderer.EntityRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(EntityRenderer.class)
public class MixinEntityRenderer_Weather {
@Inject(method = "renderRainSnow(F)V", at = @At("HEAD"), cancellable = true)
private void angelica$weather(float partialTicks, CallbackInfo ci) {
if (WeatherRenderer.render(partialTicks)) ci.cancel();
}
@Inject(method = "addRainParticles", at = @At("HEAD"), cancellable = true)
private void angelica$rainParticles(CallbackInfo ci) {
if (WeatherRenderer.spawnRainParticles()) ci.cancel();
}
}
@@ -21,13 +21,14 @@ public class MixinRenderGlobal {
*/
@Overwrite
public void renderClouds(float partialTicks) {
final IRenderHandler renderer;
if((renderer = theWorld.provider.getCloudRenderer()) != null) {
final IRenderHandler renderer = theWorld.provider.getCloudRenderer();
final boolean layered = CloudRenderer.supportsLayers(theWorld, renderer);
if (renderer != null && !layered) {
renderer.render(partialTicks, theWorld, mc);
return;
}
if(mc.theWorld.provider.isSurfaceWorld()) {
CloudRenderer.getCloudRenderer().render(cloudTickCounter, partialTicks);
if (layered || theWorld.provider.isSurfaceWorld()) {
CloudRenderer.getCloudRenderer().render(cloudTickCounter, partialTicks, renderer);
}
}
}
@@ -0,0 +1,19 @@
package com.gtnewhorizons.angelica.mixins.early.dragonapi;
import com.gtnewhorizons.angelica.compat.dragonapi.DragonAPICompat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Pseudo
@Mixin(targets = "Reika/DragonAPI/Auxiliary/Trackers/SpecialDayTracker", remap = false)
public class MixinSpecialDayTracker {
// Can't break easter eggs now can we :trolley:
@Inject(method = "loadXmasTextures", at = @At("RETURN"))
private void angelica$captureXmasFlag(CallbackInfoReturnable<Boolean> cir) {
DragonAPICompat.forcingSeasonalSnow = cir.getReturnValue();
}
}
@@ -1,9 +1,9 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.rendering.tesr.PassRebindGate;
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBlendScope;
import com.gtnewhorizons.angelica.rendering.tesr.TesrProviderDispatch;
import com.prupe.mcpatcher.ctm.CTMUtils;
import net.coderbot.iris.uniforms.CapturedRenderingState;
@@ -15,39 +15,29 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Arrays;
@Mixin(TileEntityRendererDispatcher.class)
public class MixinTileEntityRendererDispatcher {
@Unique private static int[] iris$depths = new int[4];
@Unique private static int iris$n;
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("HEAD"))
private void iris$setBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
if (iris$n == iris$depths.length) {
iris$depths = Arrays.copyOf(iris$depths, iris$n * 2);
}
iris$depths[iris$n++] = GLStateManager.pushState(StateSet.BLEND);
private void iris$beginBlockEntity(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
RenderRecovery.throwIfCrashTestArmed();
TesrBlendScope.enter();
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(iris$resolveBlockEntityId(te));
TesrAttribution.currentRenderable = te != null ? te.getClass() : null;
// Rebind Iris's pass in case the previous TESR changed it
PassRebindGate.rebindIfDirty();
}
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"))
private void iris$endBlockEntity(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
TesrAttribution.currentRenderable = null;
TesrBlendScope.exit();
}
@Unique
private static int iris$resolveBlockEntityId(TileEntity te) {
return TesrProviderDispatch.resolveBlockEntityId(te);
}
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"))
private void iris$resetBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
TesrAttribution.currentRenderable = null;
if (iris$n == 0) return;
GLStateManager.popStateTo(iris$depths[--iris$n]);
}
}