mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1babfd968c | ||
|
|
888a4de0c9 | ||
|
|
a23cefba92 | ||
|
|
f2c07dea28 | ||
|
|
53776684c8 |
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+305
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-3
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
+22
@@ -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();
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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();
|
||||
}
|
||||
}
|
||||
+13
-23
@@ -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]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user