Merge branch 'core-profile' into core-profile-dsa

This commit is contained in:
MustafarYasmin committed 2026-02-27 12:54:03 +01:00
commit 580b72829d
19 files changed
+368 -37

No files matched your search

@@ -149,7 +149,7 @@ public class CompatShaderTransformer {
injectMatrixUniforms(transformer);
transformFog(transformer, isFragment);
transformFog(transformer, isFragment, source);
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
if (source.contains("gl_FrontLightModelProduct")) {
@@ -158,6 +158,8 @@ public class CompatShaderTransformer {
}
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
if (!isFragment) {
transformer.injectVariable("out vec4 angelica_FrontColor;");
transformer.rename("gl_FrontColor", "angelica_FrontColor");
@@ -166,8 +168,10 @@ public class CompatShaderTransformer {
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
} else {
transformer.injectVariable("in vec4 angelica_FrontColor;");
transformer.rename("gl_Color", "angelica_FrontColor");
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
transformer.rename("gl_Color", "angelica_FrontColor");
}
}
// gl_TexCoord[N] varying array → per-index in/out declarations
@@ -261,13 +265,16 @@ public class CompatShaderTransformer {
}
}
private static void transformFog(Transformer transformer, boolean isFragment) {
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
if (!isFragment) {
transformer.injectVariable("out float angelica_FogFragCoord;");
transformer.prependMain("angelica_FogFragCoord = 0.0f;");
transformer.prependMain("angelica_FogFragCoord = 0.0;");
} else {
transformer.injectVariable("in float angelica_FogFragCoord;");
if (source.contains("gl_FogFragCoord")) {
transformer.injectVariable("in float angelica_FogFragCoord;");
}
}
transformer.rename("gl_Fog", "angelica_Fog");
@@ -279,7 +286,7 @@ public class CompatShaderTransformer {
transformer.injectFunction(
"angelica_FogParameters angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0f / (angelica_FogEnd - angelica_FogStart));");
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
}
/**
@@ -352,6 +359,63 @@ public class CompatShaderTransformer {
return source;
}
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
/**
* Generate a passthrough vertex shader for a fragment-only program.
*
* @param fragmentSource the transformed fragment shader source
* @return a complete vertex shader source string
*/
public static String generatePassthroughVertexShader(String fragmentSource) {
final StringBuilder sb = new StringBuilder(512);
sb.append("#version 330 core\n\n");
// Always have position
sb.append("layout(location = 0) in vec4 a_Position;\n");
// Matrix uniforms for position transform
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder();
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
sb.append("layout(location = 1) in vec4 a_Color;\n");
varyings.append("out vec4 angelica_FrontColor;\n");
assignments.append(" angelica_FrontColor = a_Color;\n");
}
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
final Set<Integer> texCoordIndices = new HashSet<>();
while (texCoordMatcher.find()) {
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
}
for (int i : texCoordIndices) {
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
final int location = i == 0 ? 2 : 3;
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
}
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
varyings.append("out float angelica_FogFragCoord;\n");
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
}
sb.append(varyings);
sb.append("\nvoid main() {\n");
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
sb.append(assignments);
sb.append("}\n");
return sb.toString();
}
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
private static String fixupVersion(String source) {
final Matcher m = VERSION_PATTERN.matcher(source);
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -38,7 +39,9 @@ public class CompatUniformManager {
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
static final int LOC_ALPHA_TEST_REF = 14;
static final int LOC_SCENE_COLOR = 15;
static final int LOC_COUNT = 16;
static final int LOC_CLIP_PLANES = 16;
static final int LOC_CLIP_PLANES_ENABLED = 17;
static final int LOC_COUNT = 18;
private static final String[] UNIFORM_NAMES = {
"angelica_ModelViewMatrix",
@@ -57,6 +60,8 @@ public class CompatUniformManager {
"iris_LightmapTextureMatrix",
"angelica_currentAlphaTest",
"angelica_SceneColor",
"angelica_ClipPlane[0]",
"angelica_ClipPlanesEnabled",
};
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
@@ -66,6 +71,7 @@ public class CompatUniformManager {
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
@@ -83,6 +89,7 @@ public class CompatUniformManager {
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
@@ -139,6 +146,15 @@ public class CompatUniformManager {
uploadSceneColor(locs);
}
}
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
@@ -261,6 +277,21 @@ public class CompatUniformManager {
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
}
@@ -497,6 +497,12 @@ public class DisplayListManager {
currentRecorder.recordMaterial(face, pname, params);
}
public static void recordClipPlane(int plane, double a, double b, double c, double d) {
if (currentRecorder == null) return;
drawBarrier();
currentRecorder.recordClipPlane(plane, a, b, c, d);
}
public static void recordStencilFunc(int func, int ref, int mask) {
if (currentRecorder == null) return;
drawBarrier();
@@ -32,6 +32,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClientArrayState;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -52,6 +53,7 @@ import net.coderbot.iris.gbuffer_overrides.state.StateTracker;
import net.coderbot.iris.gl.blending.AlphaTestStorage;
import net.coderbot.iris.gl.blending.BlendModeStorage;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import net.coderbot.iris.gl.program.ProgramUniforms;
import net.coderbot.iris.samplers.IrisSamplers;
import net.coderbot.iris.texture.pbr.PBRTextureManager;
import net.minecraft.client.Minecraft;
@@ -144,6 +146,7 @@ public class GLStateManager {
public static int lightingGeneration;
public static int fragmentGeneration; // fog + alpha ref
public static int colorGeneration; // current vertex color
public static int clipPlaneGeneration; // clip plane equation changes
// Deferred vertex attribute upload flags — set when state changes, flushed before draw
private static boolean dirtyColorAttrib;
@@ -366,6 +369,7 @@ public class GLStateManager {
// Clip plane states
@Getter protected static final BooleanStateStack[] clipPlaneStates = new BooleanStateStack[MAX_CLIP_PLANES];
@Getter protected static final ClipPlaneState clipPlaneState = new ClipPlaneState();
@Getter protected static final MatrixModeStack matrixMode = new MatrixModeStack();
@Getter protected static final Matrix4fStack modelViewMatrix = new Matrix4fStack(MAX_MODELVIEW_STACK_DEPTH);
@@ -581,6 +585,7 @@ public class GLStateManager {
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].enable();
clipPlaneGeneration++;
return;
}
@@ -660,6 +665,7 @@ public class GLStateManager {
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].disable();
clipPlaneGeneration++;
return;
}
@@ -3833,6 +3839,8 @@ public class GLStateManager {
if (caching) {
activeProgram = 0; // Track that FFP was requested
}
// Deregister Iris ValueUpdateNotifier listeners so they don't fire
ProgramUniforms.clearActiveUniforms();
ffp.activate();
return;
}
@@ -3847,6 +3855,8 @@ public class GLStateManager {
if (caching) {
activeProgram = program;
}
// Deregister Iris ValueUpdateNotifier listeners before switching programs.
ProgramUniforms.clearActiveUniforms();
if (AngelicaMod.lwjglDebug) {
final String programName = GLDebug.getObjectLabel(KHRDebug.GL_PROGRAM, program);
GLDebug.debugMessage("Activating Program - " + program + ":" + programName);
@@ -4311,9 +4321,23 @@ public class GLStateManager {
// Clip Plane Commands
public static void glClipPlane(int plane, DoubleBuffer equation) {
if (DisplayListManager.isRecording()) {
throw new UnsupportedOperationException("glClipPlane in display lists not yet implemented - if you see this, please report!");
final int pos = equation.position();
DisplayListManager.recordClipPlane(plane, equation.get(pos), equation.get(pos + 1), equation.get(pos + 2), equation.get(pos + 3));
return;
}
GL11.glClipPlane(plane, equation);
final int index = plane - GL11.GL_CLIP_PLANE0;
if (index < 0 || index >= MAX_CLIP_PLANES) return;
final int pos = equation.position();
clipPlaneState.setPlane(index, equation.get(pos), equation.get(pos + 1), equation.get(pos + 2), equation.get(pos + 3), modelViewMatrix);
clipPlaneGeneration++;
}
/** Returns true if any GL_CLIP_PLANE0..7 is currently enabled. */
public static boolean anyClipPlaneEnabled() {
for (int i = 0; i < MAX_CLIP_PLANES; i++) {
if (clipPlaneStates[i].isEnabled()) return true;
}
return false;
}
// Clear Commands
@@ -4810,10 +4834,61 @@ public class GLStateManager {
}
public static void glLinkProgram(int program) {
if (ShaderManager.getInstance().isEnabled()) {
generateVertexShaderIfNeeded(program);
}
GL20.glLinkProgram(program);
CompatUniformManager.onLinkProgram(program);
}
private static final IntBuffer SHADER_BUF = BufferUtils.createIntBuffer(8);
/**
* If a program has a fragment shader but no vertex shader, generate a passthrough
* vertex shader from the fragment's in declarations and attach it.
*/
private static void generateVertexShaderIfNeeded(int program) {
final int shaderCount = GL20.glGetProgrami(program, GL20.GL_ATTACHED_SHADERS);
if (shaderCount == 0 || shaderCount > SHADER_BUF.capacity()) return;
SHADER_BUF.clear().limit(shaderCount);
GL20.glGetAttachedShaders(program, null, SHADER_BUF);
int fragmentShader = 0;
boolean hasVertex = false;
for (int i = 0; i < shaderCount; i++) {
final int shader = SHADER_BUF.get(i);
final int type = GL20.glGetShaderi(shader, GL20.GL_SHADER_TYPE);
if (type == GL20.GL_VERTEX_SHADER) {
hasVertex = true;
break;
} else if (type == GL20.GL_FRAGMENT_SHADER) {
fragmentShader = shader;
}
}
if (hasVertex || fragmentShader == 0) return;
// Fragment-only program — generate a passthrough vertex shader
final String fragSource = GL20.glGetShaderSource(fragmentShader, 65536);
final String vertSource = CompatShaderTransformer.generatePassthroughVertexShader(fragSource);
final int vertShader = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
GL20.glShaderSource(vertShader, vertSource);
GL20.glCompileShader(vertShader);
if (GL20.glGetShaderi(vertShader, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
final String log = GL20.glGetShaderInfoLog(vertShader, 8192);
AngelicaTweaker.LOGGER.warn("CompatShaderTransformer: Generated passthrough vertex shader failed to compile:\n{}\nSource:\n{}", log, vertSource);
GL20.glDeleteShader(vertShader);
return;
}
AngelicaTweaker.LOGGER.debug("CompatShaderTransformer: Generated passthrough vertex shader for fragment-only program {}", program);
GL20.glAttachShader(program, vertShader);
GL20.glDeleteShader(vertShader);
}
public static void glDeleteProgram(int program) {
CompatUniformManager.onDeleteProgram(program);
GL20.glDeleteProgram(program);
@@ -37,10 +37,10 @@ public final class FragmentShaderGenerator {
sb.append("in vec3 v_SpecularColor;\n");
}
if (key.textureEnabled()) {
sb.append("in vec2 v_TexCoord0;\n");
sb.append("in vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec2 v_TexCoord1;\n");
sb.append("in vec4 v_TexCoord1;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
@@ -70,10 +70,10 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
if (key.textureEnabled()) {
sb.append(" vec4 texColor = texture(u_Sampler0, v_TexCoord0);\n");
sb.append(" vec4 texColor = texture(u_Sampler0, v_TexCoord0.st);\n");
}
if (key.lightmapEnabled()) {
sb.append(" vec4 lightmapColor = texture(u_Sampler1, v_TexCoord1);\n");
sb.append(" vec4 lightmapColor = texture(u_Sampler1, v_TexCoord1.st);\n");
}
}
@@ -76,6 +76,9 @@ public class Program {
public int locTexGenEyePlaneR = -1;
public int locTexGenEyePlaneQ = -1;
// Clip planes
public int locClipPlanes = -1;
// Fragment uniforms
public int locSampler0 = -1;
public int locSampler1 = -1;
@@ -143,6 +146,9 @@ public class Program {
locTexGenEyePlaneR = loc("u_TexGenEyePlaneR");
locTexGenEyePlaneQ = loc("u_TexGenEyePlaneQ");
// Clip planes
locClipPlanes = loc("u_ClipPlane[0]");
// Fragment
locSampler0 = loc("u_Sampler0");
locSampler1 = loc("u_Sampler1");
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
@@ -30,6 +31,7 @@ public class Uniforms {
private final FloatBuffer mat3Buf = memAllocFloat(9);
private final FloatBuffer vec4Buf = memAllocFloat(4);
private final FloatBuffer vec3Buf = memAllocFloat(3);
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
// Derived matrices (computed on CPU)
private final Matrix4f mvpMatrix = new Matrix4f();
@@ -57,6 +59,7 @@ public class Uniforms {
private float lastLightmapX = Float.NaN;
private float lastLightmapY = Float.NaN;
private int lastTexGenGen = -1;
private int lastClipPlaneGen = -1;
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
@@ -128,6 +131,14 @@ public class Uniforms {
}
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
uploadClipPlanes(program);
lastClipPlaneGen = cpGen;
}
}
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
uploadFragmentUniforms(program);
@@ -368,6 +379,17 @@ public class Uniforms {
GL20.glUniform4(loc, vec4Buf);
}
private void uploadClipPlanes(Program program) {
if (program.locClipPlanes == -1) return;
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
GL20.glUniform4(program.locClipPlanes, clipPlaneBuf);
}
private void uploadFragmentUniforms(Program program) {
final FragmentKey fk = program.getFragmentKey();
@@ -442,5 +464,6 @@ public class Uniforms {
memFree(mat3Buf);
memFree(vec4Buf);
memFree(vec3Buf);
memFree(clipPlaneBuf);
}
}
@@ -40,6 +40,8 @@ public final class VertexKey {
private static final int BIT_TEXGEN_T = 25;
private static final int BIT_TEXGEN_R = 28;
private static final int BIT_TEXGEN_Q = 31;
// bits 31-33: texGenModeQ (3 bits)
private static final int BIT_CLIP_PLANES = 34;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -88,6 +90,8 @@ public final class VertexKey {
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
/** Whether any texgen coordinate is enabled. */
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
/** Whether any clip plane is enabled. */
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
/** Whether vertex color replaces material ambient in this variant. */
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -214,6 +218,11 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
// Clip planes
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
return bits;
}
@@ -239,10 +248,10 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d]",
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ());
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled());
}
}
@@ -32,6 +32,9 @@ public final class VertexShaderGenerator {
if (key.fogEnabled()) {
emitFogDistance(sb, key);
}
if (key.clipPlanesEnabled()) {
emitClipDistances(sb);
}
sb.append("}\n");
return sb.toString();
@@ -110,6 +113,10 @@ public final class VertexShaderGenerator {
}
}
if (key.clipPlanesEnabled()) {
sb.append("uniform vec4 u_ClipPlane[8];\n");
}
sb.append('\n');
}
@@ -164,10 +171,10 @@ public final class VertexShaderGenerator {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("out vec2 v_TexCoord0;\n");
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec2 v_TexCoord1;\n");
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
@@ -180,8 +187,8 @@ public final class VertexShaderGenerator {
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
// Eye position needed for lighting, fog, and EYE_LINEAR texgen
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key)) {
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
sb.append('\n');
@@ -320,23 +327,23 @@ public final class VertexShaderGenerator {
sb.append(" // Texture coordinates\n");
if (key.hasVertexTexCoord()) {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = (u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0)).st;\n");
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord0 = a_TexCoord0;\n");
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
}
} else {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = (u_TextureMatrix0 * u_CurrentTexCoord).st;\n");
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
} else {
sb.append(" v_TexCoord0 = u_CurrentTexCoord.st;\n");
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
}
}
}
if (key.lightmapEnabled()) {
if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = (u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0)).st;\n");
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord1 = (u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0)).st;\n");
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
}
}
@@ -356,7 +363,7 @@ public final class VertexShaderGenerator {
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
// Always apply texture matrix when texgen is active (forced on in VertexKey)
sb.append(" v_TexCoord0 = (u_TextureMatrix0 * texGenCoord).st;\n");
sb.append(" v_TexCoord0 = u_TextureMatrix0 * texGenCoord;\n");
}
private static void emitTexGenComponent(StringBuilder sb, int mode, String swizzle, String coordName, VertexKey key) {
@@ -391,4 +398,11 @@ public final class VertexShaderGenerator {
sb.append(" v_FogCoord = abs(eyePos.z);\n");
}
}
private static void emitClipDistances(StringBuilder sb) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
}
}
}
@@ -554,6 +554,16 @@ public final class CommandBuffer {
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
@@ -10,6 +10,7 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -25,6 +26,8 @@ public final class CommandBufferExecutor {
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
// Static reusable buffer for fog/light/material params
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
// Static reusable buffer for clip plane equation
private static final DoubleBuffer PARAMS_DOUBLE_BUFFER = BufferUtils.createDoubleBuffer(4);
// Static reusable buffer for draw buffers
private static final int MAX_DRAW_BUFFERS = 8;
private static final IntBuffer DRAW_BUFFERS_BUFFER = BufferUtils.createIntBuffer(MAX_DRAW_BUFFERS);
@@ -457,6 +460,19 @@ public final class CommandBufferExecutor {
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
}
case GLCommand.CLIP_PLANE -> {
final int plane = memGetInt(ptr);
final double a = memGetDouble(ptr + 4);
final double b = memGetDouble(ptr + 12);
final double c = memGetDouble(ptr + 20);
final double d = memGetDouble(ptr + 28);
ptr += 36;
PARAMS_DOUBLE_BUFFER.clear();
PARAMS_DOUBLE_BUFFER.put(a).put(b).put(c).put(d);
PARAMS_DOUBLE_BUFFER.flip();
GLStateManager.glClipPlane(plane, PARAMS_DOUBLE_BUFFER);
}
// === Draw commands ===
case GLCommand.DRAW_RANGE -> {
final int vboIndex = memGetInt(ptr);
@@ -143,6 +143,16 @@ public final class CommandBufferProcessor {
out.writeMaterial(face, pname, fb);
}
// Clip plane command (4 doubles)
case GLCommand.CLIP_PLANE -> {
final int plane = raw.readInt();
final double a = raw.readDouble();
final double b = raw.readDouble();
final double c = raw.readDouble();
final double d = raw.readDouble();
out.writeClipPlane(plane, a, b, c, d);
}
// Draw/call commands
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
@@ -292,6 +292,11 @@ public final class CommandRecorder {
commandCount++;
}
public void recordClipPlane(int plane, double a, double b, double c, double d) {
buffer.writeClipPlane(plane, a, b, c, d);
commandCount++;
}
public void recordStencilFunc(int func, int ref, int mask) {
buffer.writeStencilFunc(func, ref, mask);
commandCount++;
@@ -89,6 +89,7 @@ public final class GLCommand {
public static final int LIGHT = 91; // [cmd:4][light:4][pname:4][count:4][params:16f] = 32 bytes
public static final int LIGHT_MODEL = 92; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int MATERIAL = 93; // [cmd:4][face:4][pname:4][count:4][params:16f] = 32 bytes
public static final int CLIP_PLANE = 94; // [cmd:4][plane:4][a:8d][b:8d][c:8d][d:8d] = 40 bytes
// === Draw commands ===
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4] = 8 bytes
@@ -179,6 +180,7 @@ public final class GLCommand {
case LIGHT -> "LIGHT";
case LIGHT_MODEL -> "LIGHT_MODEL";
case MATERIAL -> "MATERIAL";
case CLIP_PLANE -> "CLIP_PLANE";
case DRAW_RANGE -> "DRAW_RANGE";
case CALL_LIST -> "CALL_LIST";
case DRAW_BUFFER -> "DRAW_BUFFER";
@@ -254,6 +256,7 @@ public final class GLCommand {
final int count = memGetInt(ptr + 12); // light/face at +4, pname at +8, count at +12
yield 16 + count * 4;
}
case GLCommand.CLIP_PLANE -> 40; // cmd + plane + 4 doubles
default -> throw new IllegalStateException("Unknown command: " + cmd);
};
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import java.nio.FloatBuffer;
/**
* Stores eye-space clip plane equations for core profile emulation.
*/
public class ClipPlaneState {
private final float[][] eyePlanes = new float[GLStateManager.MAX_CLIP_PLANES][4];
private static final Vector4f tempPlane = new Vector4f();
private static final Matrix4f tempInverse = new Matrix4f();
public void setPlane(int index, double a, double b, double c, double d, Matrix4f modelView) {
final float[] dest = eyePlanes[index];
modelView.invert(tempInverse);
tempPlane.set((float) a, (float) b, (float) c, (float) d);
tempInverse.transformTranspose(tempPlane);
dest[0] = tempPlane.x;
dest[1] = tempPlane.y;
dest[2] = tempPlane.z;
dest[3] = tempPlane.w;
}
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
public void putEyePlane(int index, FloatBuffer buf) {
final float[] p = eyePlanes[index];
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
}
}
@@ -74,5 +74,21 @@ public class CommonTransformer {
root.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
root.renameAndWrapShadow("shadow2D", "texture");
root.renameAndWrapShadow("shadow2DLod", "textureLod");
if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
root.injectVariable("uniform bool angelica_ClipPlanesEnabled;");
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
root.appendMain(
"{ if (angelica_ClipPlanesEnabled) { vec4 _cp_ep = iris_ModelViewMatrix * iris_Vertex; "
+ "gl_ClipDistance[0] = dot(angelica_ClipPlane[0], _cp_ep); "
+ "gl_ClipDistance[1] = dot(angelica_ClipPlane[1], _cp_ep); "
+ "gl_ClipDistance[2] = dot(angelica_ClipPlane[2], _cp_ep); "
+ "gl_ClipDistance[3] = dot(angelica_ClipPlane[3], _cp_ep); "
+ "gl_ClipDistance[4] = dot(angelica_ClipPlane[4], _cp_ep); "
+ "gl_ClipDistance[5] = dot(angelica_ClipPlane[5], _cp_ep); "
+ "gl_ClipDistance[6] = dot(angelica_ClipPlane[6], _cp_ep); "
+ "gl_ClipDistance[7] = dot(angelica_ClipPlane[7], _cp_ep); } }"
);
}
}
}
@@ -12,7 +12,7 @@ public class ExternallyManagedUniforms {
addMat4(uniformHolder, "iris_ModelViewMatrix");
addMat4(uniformHolder, "iris_ProjectionMatrix");
addMat4(uniformHolder, "u_ModelViewProjectionMatrix");
addMat4(uniformHolder, "iris_NormalMatrix");
addMat3(uniformHolder, "iris_NormalMatrix");
uniformHolder.externallyManagedUniform("heavyFog", UniformType.BOOL);
}
@@ -23,6 +23,10 @@ public class ExternallyManagedUniforms {
uniformHolder.externallyManagedUniform("u_TextureScale", UniformType.VEC2);
}
private static void addMat3(UniformHolder uniformHolder, String name) {
uniformHolder.externallyManagedUniform(name, UniformType.MAT3);
}
private static void addMat4(UniformHolder uniformHolder, String name) {
uniformHolder.externallyManagedUniform(name, UniformType.MAT4);
}
@@ -51,7 +51,7 @@ public abstract class MixinForgeHooksClient_CoreProfile {
throw new RuntimeException("Failed to obtain ContextAttribs version setters", e);
}
LWJGLException cur_exception = null;
Exception cur_exception = null;
for (int major = 4; major >= 3; --major) {
final int maxMinor = (major == 4) ? 6 : 3;
final int minMinor = (major == 3) ? 3 : 0;
@@ -59,21 +59,26 @@ public abstract class MixinForgeHooksClient_CoreProfile {
try {
setMajor.invokeExact(attribs, major);
setMinor.invokeExact(attribs, minor);
Display.create(format, attribs);
return;
} catch (LWJGLException e) {
cur_exception = e;
} catch (Throwable t) {
throw new RuntimeException("Failed to set ContextAttribs version", t);
}
try {
Display.create(format, attribs);
return;
} catch (LWJGLException | RuntimeException e) {
cur_exception = e;
}
}
}
angelica$reportContextFailure(cur_exception);
throw cur_exception;
if (cur_exception instanceof LWJGLException lwjgl) {
throw lwjgl;
}
throw new LWJGLException("Failed to create OpenGL core profile context", cur_exception);
}
@Unique
private static void angelica$reportContextFailure(LWJGLException e) {
private static void angelica$reportContextFailure(Exception e) {
System.err.println("[Angelica] FATAL: Failed to create OpenGL core profile context.");
System.err.println("[Angelica] Error: " + e.getMessage());
try {
@@ -244,7 +244,7 @@ class CompatShaderTransformerTest {
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("vec4 angelica_FrontColor"), "gl_FrontColor should become a local vec4");
assertTrue(result.contains("vec4 angelica_FrontColor"), "gl_FrontColor should become angelica_FrontColor out varying");
assertFalse(result.contains("gl_FrontColor"), "gl_FrontColor should not remain");
}
@@ -494,7 +494,7 @@ class CompatShaderTransformerTest {
// Fragment shader: gl_Color → angelica_FrontColor (in varying)
assertTrue(fragResult.contains("angelica_FrontColor"), "fragment gl_Color → angelica_FrontColor");
assertFalse(fragResult.contains("angelica_Color"), "fragment should NOT have angelica_Color");
assertFalse(fragResult.contains("gl_Color"), "fragment should NOT have gl_Color");
}
@Test